/* $Id$ */
/** @file
 * VirtualBox Support Library - Hardened main(), windows bits.
 */

/*
 * Copyright (C) 2006-2020 Oracle Corporation
 *
 * This file is part of VirtualBox Open Source Edition (OSE), as
 * available from http://www.virtualbox.org. This file is free software;
 * you can redistribute it and/or modify it under the terms of the GNU
 * General Public License (GPL) as published by the Free Software
 * Foundation, in version 2 as it comes in the "COPYING" file of the
 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
 *
 * The contents of this file may alternatively be used under the terms
 * of the Common Development and Distribution License Version 1.0
 * (CDDL) only, as it comes in the "COPYING.CDDL" file of the
 * VirtualBox OSE distribution, in which case the provisions of the
 * CDDL are applicable instead of those of the GPL.
 *
 * You may elect to license modified versions of this file under the
 * terms and conditions of either the GPL or the CDDL or both.
 */


/*********************************************************************************************************************************
*   Header Files                                                                                                                 *
*********************************************************************************************************************************/
#include <iprt/nt/nt-and-windows.h>
#include <AccCtrl.h>
#include <AclApi.h>
#ifndef PROCESS_SET_LIMITED_INFORMATION
# define PROCESS_SET_LIMITED_INFORMATION        0x2000
#endif
#ifndef LOAD_LIBRARY_SEARCH_APPLICATION_DIR
# define LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR       UINT32_C(0x100)
# define LOAD_LIBRARY_SEARCH_APPLICATION_DIR    UINT32_C(0x200)
# define LOAD_LIBRARY_SEARCH_USER_DIRS          UINT32_C(0x400)
# define LOAD_LIBRARY_SEARCH_SYSTEM32           UINT32_C(0x800)
#endif

#include <VBox/sup.h>
#include <VBox/err.h>
#include <VBox/dis.h>
#include <iprt/ctype.h>
#include <iprt/string.h>
#include <iprt/initterm.h>
#include <iprt/param.h>
#include <iprt/path.h>
#include <iprt/thread.h>
#include <iprt/utf16.h>
#include <iprt/zero.h>

#include "SUPLibInternal.h"
#include "win/SUPHardenedVerify-win.h"
#include "../SUPDrvIOC.h"

#ifndef IMAGE_SCN_TYPE_NOLOAD
# define IMAGE_SCN_TYPE_NOLOAD 0x00000002
#endif


/*********************************************************************************************************************************
*   Defined Constants And Macros                                                                                                 *
*********************************************************************************************************************************/
/** The first argument of a respawed stub when respawned for the first time.
 * This just needs to be unique enough to avoid most confusion with real
 * executable names,  there are other checks in place to make sure we've respanwed. */
#define SUPR3_RESPAWN_1_ARG0  "60eaff78-4bdd-042d-2e72-669728efd737-suplib-2ndchild"

/** The first argument of a respawed stub when respawned for the second time.
 * This just needs to be unique enough to avoid most confusion with real
 * executable names,  there are other checks in place to make sure we've respanwed. */
#define SUPR3_RESPAWN_2_ARG0  "60eaff78-4bdd-042d-2e72-669728efd737-suplib-3rdchild"

/** Unconditional assertion. */
#define SUPR3HARDENED_ASSERT(a_Expr) \
    do { \
        if (!(a_Expr)) \
            supR3HardenedFatal("%s: %s\n", __FUNCTION__, #a_Expr); \
    } while (0)

/** Unconditional assertion of NT_SUCCESS. */
#define SUPR3HARDENED_ASSERT_NT_SUCCESS(a_Expr) \
    do { \
        NTSTATUS rcNtAssert = (a_Expr); \
        if (!NT_SUCCESS(rcNtAssert)) \
            supR3HardenedFatal("%s: %s -> %#x\n", __FUNCTION__, #a_Expr, rcNtAssert); \
    } while (0)

/** Unconditional assertion of a WIN32 API returning non-FALSE. */
#define SUPR3HARDENED_ASSERT_WIN32_SUCCESS(a_Expr) \
    do { \
        BOOL fRcAssert = (a_Expr); \
        if (fRcAssert == FALSE) \
            supR3HardenedFatal("%s: %s -> %#x\n", __FUNCTION__, #a_Expr, RtlGetLastWin32Error()); \
    } while (0)


/*********************************************************************************************************************************
*   Structures and Typedefs                                                                                                      *
*********************************************************************************************************************************/
/**
 * Security descriptor cleanup structure.
 */
typedef struct MYSECURITYCLEANUP
{
    union
    {
        SID                 Sid;
        uint8_t             abPadding[SECURITY_MAX_SID_SIZE];
    }                       Everyone, Owner, User, Login;
    union
    {
        ACL                 AclHdr;
        uint8_t             abPadding[1024];
    }                       Acl;
    PSECURITY_DESCRIPTOR    pSecDesc;
} MYSECURITYCLEANUP;
/** Pointer to security cleanup structure. */
typedef MYSECURITYCLEANUP *PMYSECURITYCLEANUP;


/**
 * Image verifier cache entry.
 */
typedef struct VERIFIERCACHEENTRY
{
    /** Pointer to the next entry with the same hash value. */
    struct VERIFIERCACHEENTRY * volatile pNext;
    /** Next entry in the WinVerifyTrust todo list. */
    struct VERIFIERCACHEENTRY * volatile pNextTodoWvt;

    /** The file handle. */
    HANDLE                  hFile;
    /** If fIndexNumber is set, this is an file system internal file identifier. */
    LARGE_INTEGER           IndexNumber;
    /** The path hash value. */
    uint32_t                uHash;
    /** The verification result. */
    int                     rc;
    /** Used for shutting up load and error messages after a while so they don't
     * flood the log file and fill up the disk. */
    uint32_t volatile       cHits;
    /** The validation flags (for WinVerifyTrust retry). */
    uint32_t                fFlags;
    /** Whether IndexNumber is valid  */
    bool                    fIndexNumberValid;
    /** Whether verified by WinVerifyTrust. */
    bool volatile           fWinVerifyTrust;
    /** cwcPath * sizeof(RTUTF16). */
    uint16_t                cbPath;
    /** The full path of this entry (variable size).  */
    RTUTF16                 wszPath[1];
} VERIFIERCACHEENTRY;
/** Pointer to an image verifier path entry. */
typedef VERIFIERCACHEENTRY *PVERIFIERCACHEENTRY;


/**
 * Name of an import DLL that we need to check out.
 */
typedef struct VERIFIERCACHEIMPORT
{
    /** Pointer to the next DLL in the list. */
    struct VERIFIERCACHEIMPORT * volatile pNext;
    /** The length of pwszAltSearchDir if available. */
    uint32_t                cwcAltSearchDir;
    /** This points the directory containing the DLL needing it, this will be
     * NULL for a System32 DLL. */
    PWCHAR                  pwszAltSearchDir;
    /** The name of the import DLL (variable length). */
    char                    szName[1];
} VERIFIERCACHEIMPORT;
/** Pointer to a import DLL that needs checking out. */
typedef VERIFIERCACHEIMPORT *PVERIFIERCACHEIMPORT;


/**
 * Child requests.
 */
typedef enum SUPR3WINCHILDREQ
{
    /** Perform child purification and close full access handles (must be zero). */
    kSupR3WinChildReq_PurifyChildAndCloseHandles = 0,
    /** Close the events, we're good on our own from here on.  */
    kSupR3WinChildReq_CloseEvents,
    /** Reporting error. */
    kSupR3WinChildReq_Error,
    /** End of valid requests. */
    kSupR3WinChildReq_End
} SUPR3WINCHILDREQ;

/**
 * Child process parameters.
 */
typedef struct SUPR3WINPROCPARAMS
{
    /** The event semaphore the child will be waiting on. */
    HANDLE                      hEvtChild;
    /** The event semaphore the parent will be waiting on. */
    HANDLE                      hEvtParent;

    /** The address of the NTDLL. This is only valid during the very early
     * initialization as we abuse for thread creation protection. */
    uintptr_t                   uNtDllAddr;

    /** The requested operation (set by the child). */
    SUPR3WINCHILDREQ            enmRequest;
    /** The last status. */
    int32_t                     rc;
    /** The init operation the error relates to if message, kSupInitOp_Invalid if
     *  not message. */
    SUPINITOP                   enmWhat;
    /** Where if message. */
    char                        szWhere[80];
    /** Error message / path name string space. */
    char                        szErrorMsg[16384+1024];
} SUPR3WINPROCPARAMS;


/**
 * Child process data structure for use during child process init setup and
 * purification.
 */
typedef struct SUPR3HARDNTCHILD
{
    /** Process handle. */
    HANDLE                      hProcess;
    /** Primary thread handle. */
    HANDLE                      hThread;
    /** Handle to the parent process, if we're the middle (stub) process. */
    HANDLE                      hParent;
    /** The event semaphore the child will be waiting on. */
    HANDLE                      hEvtChild;
    /** The event semaphore the parent will be waiting on. */
    HANDLE                      hEvtParent;
    /** The address of NTDLL in the child. */
    uintptr_t                   uNtDllAddr;
    /** The address of NTDLL in this process. */
    uintptr_t                   uNtDllParentAddr;
    /** Which respawn number this is (1 = stub, 2 = VM). */
    int                         iWhich;
    /** The basic process info. */
    PROCESS_BASIC_INFORMATION   BasicInfo;
    /** The probable size of the PEB. */
    size_t                      cbPeb;
    /** The pristine process environment block. */
    PEB                         Peb;
    /** The child process parameters. */
    SUPR3WINPROCPARAMS          ProcParams;
} SUPR3HARDNTCHILD;
/** Pointer to a child process data structure. */
typedef SUPR3HARDNTCHILD *PSUPR3HARDNTCHILD;


/*********************************************************************************************************************************
*   Global Variables                                                                                                             *
*********************************************************************************************************************************/
/** Process parameters.  Specified by parent if VM process, see
 *  supR3HardenedVmProcessInit. */
static SUPR3WINPROCPARAMS   g_ProcParams = { NULL, NULL, 0, (SUPR3WINCHILDREQ)0, 0 };
/** Set if supR3HardenedEarlyProcessInit was invoked. */
bool                        g_fSupEarlyProcessInit = false;
/** Set if the stub device has been opened (stub process only). */
bool                        g_fSupStubOpened = false;

/** @name Global variables initialized by suplibHardenedWindowsMain.
 * @{ */
/** Combined windows NT version number.  See SUP_MAKE_NT_VER_COMBINED. */
uint32_t                    g_uNtVerCombined = 0;
/** Count calls to the special main function for linking santity checks. */
static uint32_t volatile    g_cSuplibHardenedWindowsMainCalls;
/** The UTF-16 windows path to the executable. */
RTUTF16                     g_wszSupLibHardenedExePath[1024];
/** The NT path of the executable. */
SUPSYSROOTDIRBUF            g_SupLibHardenedExeNtPath;
/** The NT path of the application binary directory. */
SUPSYSROOTDIRBUF            g_SupLibHardenedAppBinNtPath;
/** The offset into g_SupLibHardenedExeNtPath of the executable name (WCHAR,
 * not byte). This also gives the length of the exectuable directory path,
 * including a trailing slash. */
static uint32_t             g_offSupLibHardenedExeNtName;
/** Set if we need to use the LOAD_LIBRARY_SEARCH_USER_DIRS option. */
bool                        g_fSupLibHardenedDllSearchUserDirs = false;
/** @} */

/** @name Hook related variables.
 * @{ */
/** Pointer to the bit of assembly code that will perform the original
 *  NtCreateSection operation. */
static NTSTATUS     (NTAPI *g_pfnNtCreateSectionReal)(PHANDLE, ACCESS_MASK, POBJECT_ATTRIBUTES,
                                                      PLARGE_INTEGER, ULONG, ULONG, HANDLE);
/** Pointer to the NtCreateSection function in NtDll (for patching purposes). */
static uint8_t             *g_pbNtCreateSection;
/** The patched NtCreateSection bytes (for restoring). */
static uint8_t              g_abNtCreateSectionPatch[16];
/** Pointer to the bit of assembly code that will perform the original
 *  LdrLoadDll operation. */
static NTSTATUS     (NTAPI *g_pfnLdrLoadDllReal)(PWSTR, PULONG, PUNICODE_STRING, PHANDLE);
/** Pointer to the LdrLoadDll function in NtDll (for patching purposes). */
static uint8_t             *g_pbLdrLoadDll;
/** The patched LdrLoadDll bytes (for restoring). */
static uint8_t              g_abLdrLoadDllPatch[16];

#ifndef VBOX_WITHOUT_HARDENDED_XCPT_LOGGING
/** Pointer to the bit of assembly code that will perform the original
 *  KiUserExceptionDispatcher operation. */
static VOID        (NTAPI *g_pfnKiUserExceptionDispatcherReal)(void);
/** Pointer to the KiUserExceptionDispatcher function in NtDll (for patching purposes). */
static uint8_t             *g_pbKiUserExceptionDispatcher;
/** The patched KiUserExceptionDispatcher bytes (for restoring). */
static uint8_t              g_abKiUserExceptionDispatcherPatch[16];
#endif

/** Pointer to the bit of assembly code that will perform the original
 *  KiUserApcDispatcher operation. */
static VOID        (NTAPI *g_pfnKiUserApcDispatcherReal)(void);
/** Pointer to the KiUserApcDispatcher function in NtDll (for patching purposes). */
static uint8_t             *g_pbKiUserApcDispatcher;
/** The patched KiUserApcDispatcher bytes (for restoring). */
static uint8_t              g_abKiUserApcDispatcherPatch[16];

/** Pointer to the LdrInitializeThunk function in NtDll for
 *  supR3HardenedMonitor_KiUserApcDispatcher_C() to use for APC vetting. */
static uintptr_t            g_pfnLdrInitializeThunk;

/** The hash table of verifier cache . */
static PVERIFIERCACHEENTRY  volatile g_apVerifierCache[128];
/** Queue of cached images which needs WinVerifyTrust to check them. */
static PVERIFIERCACHEENTRY  volatile g_pVerifierCacheTodoWvt = NULL;
/** Queue of cached images which needs their imports checked. */
static PVERIFIERCACHEIMPORT volatile g_pVerifierCacheTodoImports = NULL;

/** The windows path to dir \\SystemRoot\\System32 directory (technically
 *  this whatever \\KnownDlls\\KnownDllPath points to). */
SUPSYSROOTDIRBUF            g_System32WinPath;
/** @} */

/** Positive if the DLL notification callback has been registered, counts
 * registration attempts as negative. */
static int                  g_cDllNotificationRegistered = 0;
/** The registration cookie of the DLL notification callback. */
static PVOID                g_pvDllNotificationCookie = NULL;

/** Static error info structure used during init. */
static RTERRINFOSTATIC      g_ErrInfoStatic;

/** In the assembly file. */
extern "C" uint8_t          g_abSupHardReadWriteExecPage[PAGE_SIZE];

/** Whether we've patched our own LdrInitializeThunk or not.  We do this to
 * disable thread creation. */
static bool                 g_fSupInitThunkSelfPatched;
/** The backup of our own LdrInitializeThunk code, for enabling and disabling
 * thread creation in this process. */
static uint8_t              g_abLdrInitThunkSelfBackup[16];

/** Mask of adversaries that we've detected (SUPHARDNT_ADVERSARY_XXX). */
static uint32_t             g_fSupAdversaries = 0;
/** @name SUPHARDNT_ADVERSARY_XXX - Adversaries
 * @{ */
/** Symantec endpoint protection or similar including SysPlant.sys. */
#define SUPHARDNT_ADVERSARY_SYMANTEC_SYSPLANT       RT_BIT_32(0)
/** Symantec Norton 360. */
#define SUPHARDNT_ADVERSARY_SYMANTEC_N360           RT_BIT_32(1)
/** Avast! */
#define SUPHARDNT_ADVERSARY_AVAST                   RT_BIT_32(2)
/** TrendMicro OfficeScan and probably others. */
#define SUPHARDNT_ADVERSARY_TRENDMICRO              RT_BIT_32(3)
/** TrendMicro potentially buggy sakfile.sys. */
#define SUPHARDNT_ADVERSARY_TRENDMICRO_SAKFILE      RT_BIT_32(4)
/** McAfee.  */
#define SUPHARDNT_ADVERSARY_MCAFEE                  RT_BIT_32(5)
/** Kaspersky or OEMs of it.  */
#define SUPHARDNT_ADVERSARY_KASPERSKY               RT_BIT_32(6)
/** Malwarebytes Anti-Malware (MBAM). */
#define SUPHARDNT_ADVERSARY_MBAM                    RT_BIT_32(7)
/** AVG Internet Security. */
#define SUPHARDNT_ADVERSARY_AVG                     RT_BIT_32(8)
/** Panda Security. */
#define SUPHARDNT_ADVERSARY_PANDA                   RT_BIT_32(9)
/** Microsoft Security Essentials. */
#define SUPHARDNT_ADVERSARY_MSE                     RT_BIT_32(10)
/** Comodo. */
#define SUPHARDNT_ADVERSARY_COMODO                  RT_BIT_32(11)
/** Check Point's Zone Alarm (may include Kaspersky).  */
#define SUPHARDNT_ADVERSARY_ZONE_ALARM              RT_BIT_32(12)
/** Digital guardian, old problematic version.  */
#define SUPHARDNT_ADVERSARY_DIGITAL_GUARDIAN_OLD    RT_BIT_32(13)
/** Digital guardian, new version.  */
#define SUPHARDNT_ADVERSARY_DIGITAL_GUARDIAN_NEW    RT_BIT_32(14)
/** Cylance protect or something (from googling, no available sample copy). */
#define SUPHARDNT_ADVERSARY_CYLANCE                 RT_BIT_32(15)
/** BeyondTrust / PowerBroker / something (googling, no available sample copy). */
#define SUPHARDNT_ADVERSARY_BEYONDTRUST             RT_BIT_32(16)
/** Avecto / Defendpoint / Privilege Guard (details from support guy, hoping to get sample copy). */
#define SUPHARDNT_ADVERSARY_AVECTO                  RT_BIT_32(17)
/** Sophos Endpoint Defense. */
#define SUPHARDNT_ADVERSARY_SOPHOS                  RT_BIT_32(18)
/** VMware horizon view agent. */
#define SUPHARDNT_ADVERSARY_HORIZON_VIEW_AGENT      RT_BIT_32(19)
/** Unknown adversary detected while waiting on child. */
#define SUPHARDNT_ADVERSARY_UNKNOWN                 RT_BIT_32(31)
/** @} */


/*********************************************************************************************************************************
*   Internal Functions                                                                                                           *
*********************************************************************************************************************************/
static NTSTATUS supR3HardenedScreenImage(HANDLE hFile, bool fImage, bool fIgnoreArch, PULONG pfAccess, PULONG pfProtect,
                                         bool *pfCallRealApi, const char *pszCaller, bool fAvoidWinVerifyTrust,
                                         bool *pfQuiet) RT_NOTHROW_PROTO;
static void     supR3HardenedWinRegisterDllNotificationCallback(void);
static void     supR3HardenedWinReInstallHooks(bool fFirst) RT_NOTHROW_PROTO;
DECLASM(void)   supR3HardenedEarlyProcessInitThunk(void);
DECLASM(void)   supR3HardenedMonitor_KiUserApcDispatcher(void);
#ifndef VBOX_WITHOUT_HARDENDED_XCPT_LOGGING
DECLASM(void)   supR3HardenedMonitor_KiUserExceptionDispatcher(void);
#endif
extern "C" void __stdcall suplibHardenedWindowsMain(void);


#if 0 /* unused */

/**
 * Simple wide char search routine.
 *
 * @returns Pointer to the first location of @a wcNeedle in @a pwszHaystack.
 *          NULL if not found.
 * @param   pwszHaystack    Pointer to the string that should be searched.
 * @param   wcNeedle        The character to search for.
 */
static PRTUTF16 suplibHardenedWStrChr(PCRTUTF16 pwszHaystack, RTUTF16 wcNeedle)
{
    for (;;)
    {
        RTUTF16 wcCur = *pwszHaystack;
        if (wcCur == wcNeedle)
            return (PRTUTF16)pwszHaystack;
        if (wcCur == '\0')
            return NULL;
        pwszHaystack++;
    }
}


/**
 * Simple wide char string length routine.
 *
 * @returns The number of characters in the given string. (Excludes the
 *          terminator.)
 * @param   pwsz            The string.
 */
static size_t suplibHardenedWStrLen(PCRTUTF16 pwsz)
{
    PCRTUTF16 pwszCur = pwsz;
    while (*pwszCur != '\0')
        pwszCur++;
    return pwszCur - pwsz;
}

#endif /* unused */


/**
 * Our version of GetTickCount.
 * @returns Millisecond timestamp.
 */
static uint64_t supR3HardenedWinGetMilliTS(void)
{
    PKUSER_SHARED_DATA pUserSharedData = (PKUSER_SHARED_DATA)(uintptr_t)0x7ffe0000;

    /* use interrupt time */
    LARGE_INTEGER Time;
    do
    {
        Time.HighPart = pUserSharedData->InterruptTime.High1Time;
        Time.LowPart  = pUserSharedData->InterruptTime.LowPart;
    } while (pUserSharedData->InterruptTime.High2Time != Time.HighPart);

    return (uint64_t)Time.QuadPart / 10000;
}



/**
 * Wrapper around LoadLibraryEx that deals with the UTF-8 to UTF-16 conversion
 * and supplies the right flags.
 *
 * @returns Module handle on success, NULL on failure.
 * @param   pszName             The full path to the DLL.
 * @param   fSystem32Only       Whether to only look for imports in the system32
 *                              directory.  If set to false, the application
 *                              directory is also searched.
 * @param   fMainFlags          The main flags (giving the location), if the DLL
 *                              being loaded is loaded from the app bin
 *                              directory and import other DLLs from there. Pass
 *                              0 (= SUPSECMAIN_FLAGS_LOC_APP_BIN) if not
 *                              applicable.  Ignored if @a fSystem32Only is set.
 *
 *                              This is only needed to load VBoxRT.dll when
 *                              executing a testcase from the testcase/ subdir.
 */
DECLHIDDEN(void *) supR3HardenedWinLoadLibrary(const char *pszName, bool fSystem32Only, uint32_t fMainFlags)
{
    WCHAR wszPath[RTPATH_MAX];
    PRTUTF16 pwszPath = wszPath;
    int rc = RTStrToUtf16Ex(pszName, RTSTR_MAX, &pwszPath, RT_ELEMENTS(wszPath), NULL);
    if (RT_SUCCESS(rc))
    {
        while (*pwszPath)
        {
            if (*pwszPath == '/')
                *pwszPath = '\\';
            pwszPath++;
        }

        DWORD fFlags = 0;
        if (g_uNtVerCombined >= SUP_MAKE_NT_VER_SIMPLE(6, 0))
        {
            fFlags |= LOAD_LIBRARY_SEARCH_SYSTEM32;
            if (!fSystem32Only)
            {
                fFlags |= LOAD_LIBRARY_SEARCH_APPLICATION_DIR;
                if (g_fSupLibHardenedDllSearchUserDirs)
                    fFlags |= LOAD_LIBRARY_SEARCH_USER_DIRS;
                if ((fMainFlags & SUPSECMAIN_FLAGS_LOC_MASK) != SUPSECMAIN_FLAGS_LOC_APP_BIN)
                    fFlags |= LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR;
            }
        }

        void *pvRet = (void *)LoadLibraryExW(wszPath, NULL /*hFile*/, fFlags);

        /* Vista, W7, W2K8R might not work without KB2533623, so retry with no flags. */
        if (   !pvRet
            && fFlags
            && g_uNtVerCombined < SUP_MAKE_NT_VER_SIMPLE(6, 2)
            && RtlGetLastWin32Error() == ERROR_INVALID_PARAMETER)
            pvRet = (void *)LoadLibraryExW(wszPath, NULL /*hFile*/, 0);

        return pvRet;
    }
    supR3HardenedFatal("RTStrToUtf16Ex failed on '%s': %Rrc", pszName, rc);
    /* not reached */
}


/**
 * Gets the internal index number of the file.
 *
 * @returns True if we got an index number, false if not.
 * @param   hFile           The file in question.
 * @param   pIndexNumber    where to return the index number.
 */
static bool supR3HardenedWinVerifyCacheGetIndexNumber(HANDLE hFile, PLARGE_INTEGER pIndexNumber) RT_NOTHROW_DEF
{
    IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER;
    NTSTATUS rcNt = NtQueryInformationFile(hFile, &Ios, pIndexNumber, sizeof(*pIndexNumber), FileInternalInformation);
    if (NT_SUCCESS(rcNt))
        rcNt = Ios.Status;
#ifdef DEBUG_bird
    if (!NT_SUCCESS(rcNt))
        __debugbreak();
#endif
    return NT_SUCCESS(rcNt) && pIndexNumber->QuadPart != 0;
}


/**
 * Calculates the hash value for the given UTF-16 path string.
 *
 * @returns Hash value.
 * @param   pUniStr             String to hash.
 */
static uint32_t supR3HardenedWinVerifyCacheHashPath(PCUNICODE_STRING pUniStr) RT_NOTHROW_DEF
{
    uint32_t uHash   = 0;
    unsigned cwcLeft = pUniStr->Length / sizeof(WCHAR);
    PRTUTF16 pwc     = pUniStr->Buffer;

    while (cwcLeft-- > 0)
    {
        RTUTF16 wc = *pwc++;
        if (wc < 0x80)
            wc = wc != '/' ? RT_C_TO_LOWER(wc) : '\\';
        uHash = wc + (uHash << 6) + (uHash << 16) - uHash;
    }
    return uHash;
}


/**
 * Calculates the hash value for a directory + filename combo as if they were
 * one single string.
 *
 * @returns Hash value.
 * @param   pawcDir             The directory name.
 * @param   cwcDir              The length of the directory name. RTSTR_MAX if
 *                              not available.
 * @param   pszName             The import name (UTF-8).
 */
static uint32_t supR3HardenedWinVerifyCacheHashDirAndFile(PCRTUTF16 pawcDir, uint32_t cwcDir, const char *pszName) RT_NOTHROW_DEF
{
    uint32_t uHash = 0;
    while (cwcDir-- > 0)
    {
        RTUTF16 wc = *pawcDir++;
        if (wc < 0x80)
            wc = wc != '/' ? RT_C_TO_LOWER(wc) : '\\';
        uHash = wc + (uHash << 6) + (uHash << 16) - uHash;
    }

    unsigned char ch = '\\';
    uHash = ch + (uHash << 6) + (uHash << 16) - uHash;

    while ((ch = *pszName++) != '\0')
    {
        ch = RT_C_TO_LOWER(ch);
        uHash = ch + (uHash << 6) + (uHash << 16) - uHash;
    }

    return uHash;
}


/**
 * Verify string cache compare function.
 *
 * @returns true if the strings match, false if not.
 * @param   pawcLeft            The left hand string.
 * @param   pawcRight           The right hand string.
 * @param   cwcToCompare        The number of chars to compare.
 */
static bool supR3HardenedWinVerifyCacheIsMatch(PCRTUTF16 pawcLeft, PCRTUTF16 pawcRight, uint32_t cwcToCompare) RT_NOTHROW_DEF
{
    /* Try a quick memory compare first. */
    if (memcmp(pawcLeft, pawcRight, cwcToCompare * sizeof(RTUTF16)) == 0)
        return true;

    /* Slow char by char compare. */
    while (cwcToCompare-- > 0)
    {
        RTUTF16 wcLeft  = *pawcLeft++;
        RTUTF16 wcRight = *pawcRight++;
        if (wcLeft != wcRight)
        {
            wcLeft  = wcLeft  != '/' ? RT_C_TO_LOWER(wcLeft)  : '\\';
            wcRight = wcRight != '/' ? RT_C_TO_LOWER(wcRight) : '\\';
            if (wcLeft != wcRight)
                return false;
        }
    }

    return true;
}



/**
 * Inserts the given verifier result into the cache.
 *
 * @param   pUniStr             The full path of the image.
 * @param   hFile               The file handle - must either be entered into
 *                              the cache or closed.
 * @param   rc                  The verifier result.
 * @param   fWinVerifyTrust     Whether verified by WinVerifyTrust or not.
 * @param   fFlags              The image verification flags.
 */
static void supR3HardenedWinVerifyCacheInsert(PCUNICODE_STRING pUniStr, HANDLE hFile, int rc,
                                              bool fWinVerifyTrust, uint32_t fFlags) RT_NOTHROW_DEF
{
    /*
     * Allocate and initalize a new entry.
     */
    PVERIFIERCACHEENTRY pEntry = (PVERIFIERCACHEENTRY)RTMemAllocZ(sizeof(VERIFIERCACHEENTRY) + pUniStr->Length);
    if (pEntry)
    {
        pEntry->pNext           = NULL;
        pEntry->pNextTodoWvt    = NULL;
        pEntry->hFile           = hFile;
        pEntry->uHash           = supR3HardenedWinVerifyCacheHashPath(pUniStr);
        pEntry->rc              = rc;
        pEntry->fFlags          = fFlags;
        pEntry->cHits           = 0;
        pEntry->fWinVerifyTrust = fWinVerifyTrust;
        pEntry->cbPath          = pUniStr->Length;
        memcpy(pEntry->wszPath, pUniStr->Buffer, pUniStr->Length);
        pEntry->wszPath[pUniStr->Length / sizeof(WCHAR)] = '\0';
        pEntry->fIndexNumberValid = supR3HardenedWinVerifyCacheGetIndexNumber(hFile, &pEntry->IndexNumber);

        /*
         * Try insert it, careful with concurrent code as well as potential duplicates.
         */
        uint32_t iHashTab = pEntry->uHash % RT_ELEMENTS(g_apVerifierCache);
        VERIFIERCACHEENTRY * volatile *ppEntry = &g_apVerifierCache[iHashTab];
        for (;;)
        {
            if (ASMAtomicCmpXchgPtr(ppEntry, pEntry, NULL))
            {
                if (!fWinVerifyTrust)
                    do
                        pEntry->pNextTodoWvt = g_pVerifierCacheTodoWvt;
                    while (!ASMAtomicCmpXchgPtr(&g_pVerifierCacheTodoWvt, pEntry, pEntry->pNextTodoWvt));

                SUP_DPRINTF(("supR3HardenedWinVerifyCacheInsert: %ls\n", pUniStr->Buffer));
                return;
            }

            PVERIFIERCACHEENTRY pOther = *ppEntry;
            if (!pOther)
                continue;
            if (   pOther->uHash  == pEntry->uHash
                && pOther->cbPath == pEntry->cbPath
                && supR3HardenedWinVerifyCacheIsMatch(pOther->wszPath, pEntry->wszPath, pEntry->cbPath / sizeof(RTUTF16)))
                break;
            ppEntry = &pOther->pNext;
        }

        /* Duplicate entry (may happen due to races). */
        RTMemFree(pEntry);
    }
    NtClose(hFile);
}


/**
 * Looks up an entry in the verifier hash table.
 *
 * @return  Pointer to the entry on if found, NULL if not.
 * @param   pUniStr             The full path of the image.
 * @param   hFile               The file handle.
 */
static PVERIFIERCACHEENTRY supR3HardenedWinVerifyCacheLookup(PCUNICODE_STRING pUniStr, HANDLE hFile) RT_NOTHROW_DEF
{
    PRTUTF16 const      pwszPath = pUniStr->Buffer;
    uint16_t const      cbPath   = pUniStr->Length;
    uint32_t            uHash    = supR3HardenedWinVerifyCacheHashPath(pUniStr);
    uint32_t            iHashTab = uHash % RT_ELEMENTS(g_apVerifierCache);
    PVERIFIERCACHEENTRY pCur     = g_apVerifierCache[iHashTab];
    while (pCur)
    {
        if (   pCur->uHash  == uHash
            && pCur->cbPath == cbPath
            && supR3HardenedWinVerifyCacheIsMatch(pCur->wszPath, pwszPath, cbPath / sizeof(RTUTF16)))
        {

            if (!pCur->fIndexNumberValid)
                return pCur;
            LARGE_INTEGER IndexNumber;
            bool fIndexNumberValid = supR3HardenedWinVerifyCacheGetIndexNumber(hFile, &IndexNumber);
            if (   fIndexNumberValid
                && IndexNumber.QuadPart == pCur->IndexNumber.QuadPart)
                return pCur;
#ifdef DEBUG_bird
            __debugbreak();
#endif
        }
        pCur = pCur->pNext;
    }
    return NULL;
}


/**
 * Looks up an import DLL in the verifier hash table.
 *
 * @return  Pointer to the entry on if found, NULL if not.
 * @param   pawcDir             The directory name.
 * @param   cwcDir              The length of the directory name.
 * @param   pszName             The import name (UTF-8).
 */
static PVERIFIERCACHEENTRY supR3HardenedWinVerifyCacheLookupImport(PCRTUTF16 pawcDir, uint32_t cwcDir, const char *pszName)
{
    uint32_t            uHash    = supR3HardenedWinVerifyCacheHashDirAndFile(pawcDir, cwcDir, pszName);
    uint32_t            iHashTab = uHash % RT_ELEMENTS(g_apVerifierCache);
    uint32_t const      cbPath   = (uint32_t)((cwcDir + 1 + strlen(pszName)) * sizeof(RTUTF16));
    PVERIFIERCACHEENTRY pCur     = g_apVerifierCache[iHashTab];
    while (pCur)
    {
        if (   pCur->uHash  == uHash
            && pCur->cbPath == cbPath)
        {
            if (supR3HardenedWinVerifyCacheIsMatch(pCur->wszPath, pawcDir, cwcDir))
            {
                if (pCur->wszPath[cwcDir] == '\\' || pCur->wszPath[cwcDir] == '/')
                {
                    if (RTUtf16ICmpAscii(&pCur->wszPath[cwcDir + 1], pszName))
                    {
                        return pCur;
                    }
                }
            }
        }

        pCur = pCur->pNext;
    }
    return NULL;
}


/**
 * Schedules the import DLLs for verification and entry into the cache.
 *
 * @param   hLdrMod             The loader module which imports should be
 *                              scheduled for verification.
 * @param   pwszName            The full NT path of the module.
 */
DECLHIDDEN(void) supR3HardenedWinVerifyCacheScheduleImports(RTLDRMOD hLdrMod, PCRTUTF16 pwszName)
{
    /*
     * Any imports?
     */
    uint32_t cImports;
    int rc = RTLdrQueryPropEx(hLdrMod, RTLDRPROP_IMPORT_COUNT, NULL /*pvBits*/, &cImports, sizeof(cImports), NULL);
    if (RT_SUCCESS(rc))
    {
        if (cImports)
        {
            /*
             * Figure out the DLL directory from pwszName.
             */
            PCRTUTF16 pawcDir = pwszName;
            uint32_t  cwcDir = 0;
            uint32_t  i = 0;
            RTUTF16   wc;
            while ((wc = pawcDir[i++]) != '\0')
                if ((wc == '\\' || wc == '/' || wc == ':') && cwcDir + 2 != i)
                    cwcDir = i - 1;
            if (   g_System32NtPath.UniStr.Length / sizeof(WCHAR) == cwcDir
                && supR3HardenedWinVerifyCacheIsMatch(pawcDir, g_System32NtPath.UniStr.Buffer, cwcDir))
                pawcDir = NULL;

            /*
             * Enumerate the imports.
             */
            for (i = 0; i < cImports; i++)
            {
                union
                {
                    char        szName[256];
                    uint32_t    iImport;
                } uBuf;
                uBuf.iImport = i;
                rc = RTLdrQueryPropEx(hLdrMod, RTLDRPROP_IMPORT_MODULE, NULL /*pvBits*/, &uBuf, sizeof(uBuf), NULL);
                if (RT_SUCCESS(rc))
                {
                    /*
                     * Skip kernel32, ntdll and API set stuff.
                     */
                    RTStrToLower(uBuf.szName);
                    if (   RTStrCmp(uBuf.szName, "kernel32.dll") == 0
                        || RTStrCmp(uBuf.szName, "kernelbase.dll") == 0
                        || RTStrCmp(uBuf.szName, "ntdll.dll") == 0
                        || RTStrNCmp(uBuf.szName, RT_STR_TUPLE("api-ms-win-")) == 0
                        || RTStrNCmp(uBuf.szName, RT_STR_TUPLE("ext-ms-win-")) == 0
                       )
                    {
                        continue;
                    }

                    /*
                     * Skip to the next one if it's already in the cache.
                     */
                    if (supR3HardenedWinVerifyCacheLookupImport(g_System32NtPath.UniStr.Buffer,
                                                                g_System32NtPath.UniStr.Length / sizeof(WCHAR),
                                                                uBuf.szName) != NULL)
                    {
                        SUP_DPRINTF(("supR3HardenedWinVerifyCacheScheduleImports: '%s' cached for system32\n", uBuf.szName));
                        continue;
                    }
                    if (supR3HardenedWinVerifyCacheLookupImport(g_SupLibHardenedAppBinNtPath.UniStr.Buffer,
                                                                g_SupLibHardenedAppBinNtPath.UniStr.Length / sizeof(CHAR),
                                                                uBuf.szName) != NULL)
                    {
                        SUP_DPRINTF(("supR3HardenedWinVerifyCacheScheduleImports: '%s' cached for appdir\n", uBuf.szName));
                        continue;
                    }
                    if (pawcDir && supR3HardenedWinVerifyCacheLookupImport(pawcDir, cwcDir, uBuf.szName) != NULL)
                    {
                        SUP_DPRINTF(("supR3HardenedWinVerifyCacheScheduleImports: '%s' cached for dll dir\n", uBuf.szName));
                        continue;
                    }

                    /* We could skip already scheduled modules, but that'll require serialization and extra work... */

                    /*
                     * Add it to the todo list.
                     */
                    SUP_DPRINTF(("supR3HardenedWinVerifyCacheScheduleImports: Import todo: #%u '%s'.\n", i, uBuf.szName));
                    uint32_t cbName        = (uint32_t)strlen(uBuf.szName) + 1;
                    uint32_t cbNameAligned = RT_ALIGN_32(cbName, sizeof(RTUTF16));
                    uint32_t cbNeeded      = RT_UOFFSETOF_DYN(VERIFIERCACHEIMPORT, szName[cbNameAligned])
                                           + (pawcDir ? (cwcDir + 1) * sizeof(RTUTF16) : 0);
                    PVERIFIERCACHEIMPORT pImport = (PVERIFIERCACHEIMPORT)RTMemAllocZ(cbNeeded);
                    if (pImport)
                    {
                        /* Init it. */
                        memcpy(pImport->szName, uBuf.szName, cbName);
                        if (!pawcDir)
                        {
                            pImport->cwcAltSearchDir  = 0;
                            pImport->pwszAltSearchDir = NULL;
                        }
                        else
                        {
                            pImport->cwcAltSearchDir = cwcDir;
                            pImport->pwszAltSearchDir = (PRTUTF16)&pImport->szName[cbNameAligned];
                            memcpy(pImport->pwszAltSearchDir, pawcDir, cwcDir * sizeof(RTUTF16));
                            pImport->pwszAltSearchDir[cwcDir] = '\0';
                        }

                        /* Insert it. */
                        do
                            pImport->pNext = g_pVerifierCacheTodoImports;
                        while (!ASMAtomicCmpXchgPtr(&g_pVerifierCacheTodoImports, pImport, pImport->pNext));
                    }
                }
                else
                    SUP_DPRINTF(("RTLDRPROP_IMPORT_MODULE failed with rc=%Rrc i=%#x on '%ls'\n", rc, i, pwszName));
            }
        }
        else
            SUP_DPRINTF(("'%ls' has no imports\n", pwszName));
    }
    else
        SUP_DPRINTF(("RTLDRPROP_IMPORT_COUNT failed with rc=%Rrc on '%ls'\n", rc, pwszName));
}


/**
 * Processes the list of import todos.
 */
static void supR3HardenedWinVerifyCacheProcessImportTodos(void)
{
    /*
     * Work until we've got nothing more todo.
     */
    for (;;)
    {
        PVERIFIERCACHEIMPORT pTodo = ASMAtomicXchgPtrT(&g_pVerifierCacheTodoImports, NULL, PVERIFIERCACHEIMPORT);
        if (!pTodo)
            break;
        do
        {
            PVERIFIERCACHEIMPORT pCur = pTodo;
            pTodo = pTodo->pNext;

            /*
             * Not in the cached already?
             */
            if (   !supR3HardenedWinVerifyCacheLookupImport(g_System32NtPath.UniStr.Buffer,
                                                            g_System32NtPath.UniStr.Length / sizeof(WCHAR),
                                                            pCur->szName)
                && !supR3HardenedWinVerifyCacheLookupImport(g_SupLibHardenedAppBinNtPath.UniStr.Buffer,
                                                            g_SupLibHardenedAppBinNtPath.UniStr.Length / sizeof(WCHAR),
                                                            pCur->szName)
                && (   pCur->cwcAltSearchDir == 0
                    || !supR3HardenedWinVerifyCacheLookupImport(pCur->pwszAltSearchDir, pCur->cwcAltSearchDir, pCur->szName)) )
            {
                /*
                 * Try locate the imported DLL and open it.
                 */
                SUP_DPRINTF(("supR3HardenedWinVerifyCacheProcessImportTodos: Processing '%s'...\n", pCur->szName));

                NTSTATUS    rcNt;
                NTSTATUS    rcNtRedir = 0x22222222;
                HANDLE      hFile = INVALID_HANDLE_VALUE;
                RTUTF16     wszPath[260 + 260]; /* Assumes we've limited the import name length to 256. */
                AssertCompile(sizeof(wszPath) > sizeof(g_System32NtPath));

                /*
                 * Check for DLL isolation / redirection / mapping.
                 */
                size_t      cwcName  = 260;
                PRTUTF16    pwszName = &wszPath[0];
                int rc = RTStrToUtf16Ex(pCur->szName, RTSTR_MAX, &pwszName, cwcName, &cwcName);
                if (RT_SUCCESS(rc))
                {
                    UNICODE_STRING UniStrName;
                    UniStrName.Buffer = wszPath;
                    UniStrName.Length = (USHORT)cwcName * sizeof(WCHAR);
                    UniStrName.MaximumLength = UniStrName.Length + sizeof(WCHAR);

                    UNICODE_STRING UniStrStatic;
                    UniStrStatic.Buffer = &wszPath[cwcName + 1];
                    UniStrStatic.Length = 0;
                    UniStrStatic.MaximumLength = (USHORT)(sizeof(wszPath) - cwcName * sizeof(WCHAR) - sizeof(WCHAR));

                    static UNICODE_STRING const s_DefaultSuffix = RTNT_CONSTANT_UNISTR(L".dll");
                    UNICODE_STRING  UniStrDynamic = { 0, 0, NULL };
                    PUNICODE_STRING pUniStrResult = NULL;

                    rcNtRedir = RtlDosApplyFileIsolationRedirection_Ustr(1 /*fFlags*/,
                                                                         &UniStrName,
                                                                         (PUNICODE_STRING)&s_DefaultSuffix,
                                                                         &UniStrStatic,
                                                                         &UniStrDynamic,
                                                                         &pUniStrResult,
                                                                         NULL /*pNewFlags*/,
                                                                         NULL /*pcbFilename*/,
                                                                         NULL /*pcbNeeded*/);
                    if (NT_SUCCESS(rcNtRedir))
                    {
                        IO_STATUS_BLOCK     Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER;
                        OBJECT_ATTRIBUTES   ObjAttr;
                        InitializeObjectAttributes(&ObjAttr, pUniStrResult,
                                                   OBJ_CASE_INSENSITIVE, NULL /*hRootDir*/, NULL /*pSecDesc*/);
                        rcNt = NtCreateFile(&hFile,
                                            FILE_READ_DATA | READ_CONTROL | SYNCHRONIZE,
                                            &ObjAttr,
                                            &Ios,
                                            NULL /* Allocation Size*/,
                                            FILE_ATTRIBUTE_NORMAL,
                                            FILE_SHARE_READ,
                                            FILE_OPEN,
                                            FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT,
                                            NULL /*EaBuffer*/,
                                            0 /*EaLength*/);
                        if (NT_SUCCESS(rcNt))
                            rcNt = Ios.Status;
                        if (NT_SUCCESS(rcNt))
                        {
                            /* For accurate logging. */
                            size_t cwcCopy = RT_MIN(pUniStrResult->Length / sizeof(RTUTF16), RT_ELEMENTS(wszPath) - 1);
                            memcpy(wszPath, pUniStrResult->Buffer, cwcCopy * sizeof(RTUTF16));
                            wszPath[cwcCopy] = '\0';
                        }
                        else
                            hFile = INVALID_HANDLE_VALUE;
                        RtlFreeUnicodeString(&UniStrDynamic);
                    }
                }
                else
                    SUP_DPRINTF(("supR3HardenedWinVerifyCacheProcessImportTodos: RTStrToUtf16Ex #1 failed: %Rrc\n", rc));

                /*
                 * If not something that gets remapped, do the half normal searching we need.
                 */
                if (hFile == INVALID_HANDLE_VALUE)
                {
                    struct
                    {
                        PRTUTF16 pawcDir;
                        uint32_t cwcDir;
                    } Tmp, aDirs[] =
                    {
                        { g_System32NtPath.UniStr.Buffer,           g_System32NtPath.UniStr.Length / sizeof(WCHAR) },
                        { g_SupLibHardenedExeNtPath.UniStr.Buffer,  g_SupLibHardenedAppBinNtPath.UniStr.Length / sizeof(WCHAR) },
                        { pCur->pwszAltSearchDir,                   pCur->cwcAltSearchDir },
                    };

                    /* Search System32 first, unless it's a 'V*' or 'm*' name, the latter for msvcrt.  */
                    if (   pCur->szName[0] == 'v'
                        || pCur->szName[0] == 'V'
                        || pCur->szName[0] == 'm'
                        || pCur->szName[0] == 'M')
                    {
                        Tmp      = aDirs[0];
                        aDirs[0] = aDirs[1];
                        aDirs[1] = Tmp;
                    }

                    for (uint32_t i = 0; i < RT_ELEMENTS(aDirs); i++)
                    {
                        if (aDirs[i].pawcDir && aDirs[i].cwcDir && aDirs[i].cwcDir < RT_ELEMENTS(wszPath) / 3 * 2)
                        {
                            memcpy(wszPath, aDirs[i].pawcDir, aDirs[i].cwcDir * sizeof(RTUTF16));
                            uint32_t cwc = aDirs[i].cwcDir;
                            wszPath[cwc++] = '\\';
                            cwcName  = RT_ELEMENTS(wszPath) - cwc;
                            pwszName = &wszPath[cwc];
                            rc = RTStrToUtf16Ex(pCur->szName, RTSTR_MAX, &pwszName, cwcName, &cwcName);
                            if (RT_SUCCESS(rc))
                            {
                                IO_STATUS_BLOCK     Ios   = RTNT_IO_STATUS_BLOCK_INITIALIZER;
                                UNICODE_STRING      NtName;
                                NtName.Buffer        = wszPath;
                                NtName.Length        = (USHORT)((cwc + cwcName) * sizeof(WCHAR));
                                NtName.MaximumLength = NtName.Length + sizeof(WCHAR);
                                OBJECT_ATTRIBUTES   ObjAttr;
                                InitializeObjectAttributes(&ObjAttr, &NtName, OBJ_CASE_INSENSITIVE, NULL /*hRootDir*/, NULL /*pSecDesc*/);

                                rcNt = NtCreateFile(&hFile,
                                                    FILE_READ_DATA | READ_CONTROL | SYNCHRONIZE,
                                                    &ObjAttr,
                                                    &Ios,
                                                    NULL /* Allocation Size*/,
                                                    FILE_ATTRIBUTE_NORMAL,
                                                    FILE_SHARE_READ,
                                                    FILE_OPEN,
                                                    FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT,
                                                    NULL /*EaBuffer*/,
                                                    0 /*EaLength*/);
                                if (NT_SUCCESS(rcNt))
                                    rcNt = Ios.Status;
                                if (NT_SUCCESS(rcNt))
                                    break;
                                hFile = INVALID_HANDLE_VALUE;
                            }
                            else
                                SUP_DPRINTF(("supR3HardenedWinVerifyCacheProcessImportTodos: RTStrToUtf16Ex #2 failed: %Rrc\n", rc));
                        }
                    }
                }

                /*
                 * If we successfully opened it, verify it and cache the result.
                 */
                if (hFile != INVALID_HANDLE_VALUE)
                {
                    SUP_DPRINTF(("supR3HardenedWinVerifyCacheProcessImportTodos: '%s' -> '%ls' [rcNtRedir=%#x]\n",
                                 pCur->szName, wszPath, rcNtRedir));

                    ULONG fAccess = 0;
                    ULONG fProtect = 0;
                    bool  fCallRealApi = false;
                    rcNt = supR3HardenedScreenImage(hFile, true /*fImage*/, false /*fIgnoreArch*/, &fAccess, &fProtect,
                                                    &fCallRealApi, "Imports", false /*fAvoidWinVerifyTrust*/, NULL /*pfQuiet*/);
                    NtClose(hFile);
                }
                else
                    SUP_DPRINTF(("supR3HardenedWinVerifyCacheProcessImportTodos: Failed to locate '%s'\n", pCur->szName));
            }
            else
                SUP_DPRINTF(("supR3HardenedWinVerifyCacheProcessImportTodos: '%s' is in the cache.\n", pCur->szName));

            RTMemFree(pCur);
        } while (pTodo);
    }
}


/**
 * Processes the list of WinVerifyTrust todos.
 */
static void supR3HardenedWinVerifyCacheProcessWvtTodos(void)
{
    PVERIFIERCACHEENTRY           pReschedule = NULL;
    PVERIFIERCACHEENTRY volatile *ppReschedLastNext = &pReschedule;

    /*
     * Work until we've got nothing more todo.
     */
    for (;;)
    {
        if (!supHardenedWinIsWinVerifyTrustCallable())
            break;
        PVERIFIERCACHEENTRY pTodo = ASMAtomicXchgPtrT(&g_pVerifierCacheTodoWvt, NULL, PVERIFIERCACHEENTRY);
        if (!pTodo)
            break;
        do
        {
            PVERIFIERCACHEENTRY pCur = pTodo;
            pTodo = pTodo->pNextTodoWvt;
            pCur->pNextTodoWvt = NULL;

            if (   !pCur->fWinVerifyTrust
                && RT_SUCCESS(pCur->rc))
            {
                bool fWinVerifyTrust = false;
                int rc = supHardenedWinVerifyImageTrust(pCur->hFile, pCur->wszPath, pCur->fFlags, pCur->rc,
                                                        &fWinVerifyTrust, NULL /* pErrInfo*/);
                if (RT_FAILURE(rc) || fWinVerifyTrust)
                {
                    SUP_DPRINTF(("supR3HardenedWinVerifyCacheProcessWvtTodos: %d (was %d) fWinVerifyTrust=%d for '%ls'\n",
                                 rc, pCur->rc, fWinVerifyTrust, pCur->wszPath));
                    pCur->fWinVerifyTrust = true;
                    pCur->rc = rc;
                }
                else
                {
                    /* Retry it at a later time. */
                    SUP_DPRINTF(("supR3HardenedWinVerifyCacheProcessWvtTodos: %d (was %d) fWinVerifyTrust=%d for '%ls' [rescheduled]\n",
                                 rc, pCur->rc, fWinVerifyTrust, pCur->wszPath));
                    *ppReschedLastNext = pCur;
                    ppReschedLastNext = &pCur->pNextTodoWvt;
                }
            }
            /* else: already processed. */
        } while (pTodo);
    }

    /*
     * Anything to reschedule.
     */
    if (pReschedule)
    {
        do
            *ppReschedLastNext = g_pVerifierCacheTodoWvt;
        while (!ASMAtomicCmpXchgPtr(&g_pVerifierCacheTodoWvt, pReschedule, *ppReschedLastNext));
    }
}


/**
 * Translates VBox status code (from supHardenedWinVerifyImageTrust) to an NT
 * status.
 *
 * @returns NT status.
 * @param   rc                      VBox status code.
 */
static NTSTATUS supR3HardenedScreenImageCalcStatus(int rc) RT_NOTHROW_DEF
{
    /* This seems to be what LdrLoadDll returns when loading a 32-bit DLL into
       a 64-bit process.  At least here on windows 10 (2015-11-xx).

       NtCreateSection probably returns something different, possibly a warning,
       we currently don't distinguish between the too, so we stick with the
       LdrLoadDll one as it's definitely an error.*/
    if (rc == VERR_LDR_ARCH_MISMATCH)
        return STATUS_INVALID_IMAGE_FORMAT;

    return STATUS_TRUST_FAILURE;
}


/**
 * Screens an image file or file mapped with execute access.
 *
 * @returns NT status code.
 * @param   hFile                   The file handle.
 * @param   fImage                  Set if image file mapping being made
 *                                  (NtCreateSection thing).
 * @param   fIgnoreArch             Using the DONT_RESOLVE_DLL_REFERENCES flag,
 *                                  which also implies that DLL init / term code
 *                                  isn't called, so the architecture should be
 *                                  ignored.
 * @param   pfAccess                Pointer to the NtCreateSection access flags,
 *                                  so we can modify them if necessary.
 * @param   pfProtect               Pointer to the NtCreateSection protection
 *                                  flags, so we can modify them if necessary.
 * @param   pfCallRealApi           Whether it's ok to go on to the real API.
 * @param   pszCaller               Who is calling (for debugging / logging).
 * @param   fAvoidWinVerifyTrust    Whether we should avoid WinVerifyTrust.
 * @param   pfQuiet                 Where to return whether to be quiet about
 *                                  this image in the log (i.e. we've seen it
 *                                  lots of times already).  Optional.
 */
static NTSTATUS
supR3HardenedScreenImage(HANDLE hFile, bool fImage, bool fIgnoreArch, PULONG pfAccess, PULONG pfProtect,
                         bool *pfCallRealApi, const char *pszCaller, bool fAvoidWinVerifyTrust, bool *pfQuiet) RT_NOTHROW_DEF
{
    *pfCallRealApi = false;
    if (pfQuiet)
        *pfQuiet = false;

    /*
     * Query the name of the file, making sure to zero terminator the
     * string. (2nd half of buffer is used for error info, see below.)
     */
    union
    {
        UNICODE_STRING UniStr;
        uint8_t abBuffer[sizeof(UNICODE_STRING) + 2048 * sizeof(WCHAR)];
    } uBuf;
    RT_ZERO(uBuf);
    ULONG cbNameBuf;
    NTSTATUS rcNt = NtQueryObject(hFile, ObjectNameInformation, &uBuf, sizeof(uBuf) - sizeof(WCHAR) - 128, &cbNameBuf);
    if (!NT_SUCCESS(rcNt))
    {
        supR3HardenedError(VINF_SUCCESS, false,
                           "supR3HardenedScreenImage/%s: NtQueryObject -> %#x (fImage=%d fProtect=%#x fAccess=%#x)\n",
                           pszCaller, fImage, *pfProtect, *pfAccess);
        return rcNt;
    }

    if (!RTNtPathFindPossible8dot3Name(uBuf.UniStr.Buffer))
        cbNameBuf += sizeof(WCHAR);
    else
    {
        uBuf.UniStr.MaximumLength = sizeof(uBuf) - 128;
        RTNtPathExpand8dot3Path(&uBuf.UniStr, true /*fPathOnly*/);
        cbNameBuf = (uintptr_t)uBuf.UniStr.Buffer + uBuf.UniStr.Length + sizeof(WCHAR) - (uintptr_t)&uBuf.abBuffer[0];
    }

    /*
     * Check the cache.
     */
    PVERIFIERCACHEENTRY pCacheHit = supR3HardenedWinVerifyCacheLookup(&uBuf.UniStr, hFile);
    if (pCacheHit)
    {
        /* Do hit accounting and figure whether we need to be quiet or not. */
        uint32_t   cHits  = ASMAtomicIncU32(&pCacheHit->cHits);
        bool const fQuiet = cHits >= 8 && !RT_IS_POWER_OF_TWO(cHits);
        if (pfQuiet)
            *pfQuiet = fQuiet;

        /* If we haven't done the WinVerifyTrust thing, do it if we can. */
        if (   !pCacheHit->fWinVerifyTrust
            && RT_SUCCESS(pCacheHit->rc)
            && supHardenedWinIsWinVerifyTrustCallable() )
        {
            if (!fAvoidWinVerifyTrust)
            {
                SUP_DPRINTF(("supR3HardenedScreenImage/%s: cache hit (%Rrc) on %ls [redoing WinVerifyTrust]\n",
                             pszCaller, pCacheHit->rc, pCacheHit->wszPath));

                bool fWinVerifyTrust = false;
                int rc = supHardenedWinVerifyImageTrust(pCacheHit->hFile, pCacheHit->wszPath, pCacheHit->fFlags, pCacheHit->rc,
                                                        &fWinVerifyTrust, NULL /* pErrInfo*/);
                if (RT_FAILURE(rc) || fWinVerifyTrust)
                {
                    SUP_DPRINTF(("supR3HardenedScreenImage/%s: %d (was %d) fWinVerifyTrust=%d for '%ls'\n",
                                 pszCaller, rc, pCacheHit->rc, fWinVerifyTrust, pCacheHit->wszPath));
                    pCacheHit->fWinVerifyTrust = true;
                    pCacheHit->rc = rc;
                }
                else
                    SUP_DPRINTF(("supR3HardenedScreenImage/%s: WinVerifyTrust not available, rescheduling %ls\n",
                                 pszCaller, pCacheHit->wszPath));
            }
            else
                SUP_DPRINTF(("supR3HardenedScreenImage/%s: cache hit (%Rrc) on %ls [avoiding WinVerifyTrust]\n",
                             pszCaller, pCacheHit->rc, pCacheHit->wszPath));
        }
        else if (!fQuiet || !pCacheHit->fWinVerifyTrust)
            SUP_DPRINTF(("supR3HardenedScreenImage/%s: cache hit (%Rrc) on %ls%s\n",
                         pszCaller, pCacheHit->rc, pCacheHit->wszPath, pCacheHit->fWinVerifyTrust ? "" : " [lacks WinVerifyTrust]"));

        /* Return the cached value. */
        if (RT_SUCCESS(pCacheHit->rc))
        {
            *pfCallRealApi = true;
            return STATUS_SUCCESS;
        }

        if (!fQuiet)
            supR3HardenedError(VINF_SUCCESS, false,
                               "supR3HardenedScreenImage/%s: cached rc=%Rrc fImage=%d fProtect=%#x fAccess=%#x cHits=%u %ls\n",
                               pszCaller, pCacheHit->rc, fImage, *pfProtect, *pfAccess, cHits, uBuf.UniStr.Buffer);
        return supR3HardenedScreenImageCalcStatus(pCacheHit->rc);
    }

    /*
     * On XP the loader might hand us handles with just FILE_EXECUTE and
     * SYNCHRONIZE, the means reading will fail later on.  Also, we need
     * READ_CONTROL access to check the file ownership later on, and non
     * of the OS versions seems be giving us that.  So, in effect we
     * more or less always reopen the file here.
     */
    HANDLE hMyFile = NULL;
    rcNt = NtDuplicateObject(NtCurrentProcess(), hFile, NtCurrentProcess(),
                             &hMyFile,
                             FILE_READ_DATA | READ_CONTROL | SYNCHRONIZE,
                             0 /* Handle attributes*/, 0 /* Options */);
    if (!NT_SUCCESS(rcNt))
    {
        if (rcNt == STATUS_ACCESS_DENIED)
        {
            IO_STATUS_BLOCK     Ios   = RTNT_IO_STATUS_BLOCK_INITIALIZER;
            OBJECT_ATTRIBUTES   ObjAttr;
            InitializeObjectAttributes(&ObjAttr, &uBuf.UniStr, OBJ_CASE_INSENSITIVE, NULL /*hRootDir*/, NULL /*pSecDesc*/);

            rcNt = NtCreateFile(&hMyFile,
                                FILE_READ_DATA | READ_CONTROL | SYNCHRONIZE,
                                &ObjAttr,
                                &Ios,
                                NULL /* Allocation Size*/,
                                FILE_ATTRIBUTE_NORMAL,
                                FILE_SHARE_READ,
                                FILE_OPEN,
                                FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT,
                                NULL /*EaBuffer*/,
                                0 /*EaLength*/);
            if (NT_SUCCESS(rcNt))
                rcNt = Ios.Status;
            if (!NT_SUCCESS(rcNt))
            {
                supR3HardenedError(VINF_SUCCESS, false,
                                   "supR3HardenedScreenImage/%s: Failed to duplicate and open the file: rcNt=%#x hFile=%p %ls\n",
                                   pszCaller, rcNt, hFile, uBuf.UniStr.Buffer);
                return rcNt;
            }

            /* Check that we've got the same file. */
            LARGE_INTEGER idMyFile, idInFile;
            bool fMyValid = supR3HardenedWinVerifyCacheGetIndexNumber(hMyFile, &idMyFile);
            bool fInValid = supR3HardenedWinVerifyCacheGetIndexNumber(hFile, &idInFile);
            if (   fMyValid
                && (   fMyValid != fInValid
                    || idMyFile.QuadPart != idInFile.QuadPart))
            {
                supR3HardenedError(VINF_SUCCESS, false,
                                   "supR3HardenedScreenImage/%s: Re-opened has different ID that input: %#llx vx %#llx (%ls)\n",
                                   pszCaller, rcNt, idMyFile.QuadPart, idInFile.QuadPart, uBuf.UniStr.Buffer);
                NtClose(hMyFile);
                return STATUS_TRUST_FAILURE;
            }
        }
        else
        {
            SUP_DPRINTF(("supR3HardenedScreenImage/%s: NtDuplicateObject -> %#x\n", pszCaller, rcNt));
#ifdef DEBUG

            supR3HardenedError(VINF_SUCCESS, false,
                               "supR3HardenedScreenImage/%s: NtDuplicateObject(,%#x,) failed: %#x\n", pszCaller, hFile, rcNt);
#endif
            hMyFile = hFile;
        }
    }

    /*
     * Special Kludge for Windows XP and W2K3 and their stupid attempts
     * at mapping a hidden XML file called c:\Windows\WindowsShell.Manifest
     * with executable access.  The image bit isn't set, fortunately.
     */
    if (   !fImage
        && uBuf.UniStr.Length > g_System32NtPath.UniStr.Length - sizeof(L"System32") + sizeof(WCHAR)
        && memcmp(uBuf.UniStr.Buffer, g_System32NtPath.UniStr.Buffer,
                  g_System32NtPath.UniStr.Length - sizeof(L"System32") + sizeof(WCHAR)) == 0)
    {
        PRTUTF16 pwszName = &uBuf.UniStr.Buffer[(g_System32NtPath.UniStr.Length - sizeof(L"System32") + sizeof(WCHAR)) / sizeof(WCHAR)];
        if (RTUtf16ICmpAscii(pwszName, "WindowsShell.Manifest") == 0)
        {
            /*
             * Drop all executable access to the mapping and let it continue.
             */
            SUP_DPRINTF(("supR3HardenedScreenImage/%s: Applying the drop-exec-kludge for '%ls'\n", pszCaller, uBuf.UniStr.Buffer));
            if (*pfAccess & SECTION_MAP_EXECUTE)
                *pfAccess = (*pfAccess & ~SECTION_MAP_EXECUTE) | SECTION_MAP_READ;
            if (*pfProtect & PAGE_EXECUTE)
                *pfProtect = (*pfProtect & ~PAGE_EXECUTE) | PAGE_READONLY;
            *pfProtect = (*pfProtect & ~UINT32_C(0xf0)) | ((*pfProtect & UINT32_C(0xe0)) >> 4);
            if (hMyFile != hFile)
                NtClose(hMyFile);
            *pfCallRealApi = true;
            return STATUS_SUCCESS;
        }
    }

#ifndef VBOX_PERMIT_EVEN_MORE
    /*
     * Check the path.  We don't allow DLLs to be loaded from just anywhere:
     *      1. System32      - normal code or cat signing, owner TrustedInstaller.
     *      2. WinSxS        - normal code or cat signing, owner TrustedInstaller.
     *      3. VirtualBox    - kernel code signing and integrity checks.
     *      4. AppPatchDir   - normal code or cat signing, owner TrustedInstaller.
     *      5. Program Files - normal code or cat signing, owner TrustedInstaller.
     *      6. Common Files  - normal code or cat signing, owner TrustedInstaller.
     *      7. x86 variations of 4 & 5 - ditto.
     */
    uint32_t fFlags = 0;
    if (supHardViUniStrPathStartsWithUniStr(&uBuf.UniStr, &g_System32NtPath.UniStr, true /*fCheckSlash*/))
        fFlags |= SUPHNTVI_F_ALLOW_CAT_FILE_VERIFICATION | SUPHNTVI_F_TRUSTED_INSTALLER_OWNER;
    else if (supHardViUniStrPathStartsWithUniStr(&uBuf.UniStr, &g_WinSxSNtPath.UniStr, true /*fCheckSlash*/))
        fFlags |= SUPHNTVI_F_ALLOW_CAT_FILE_VERIFICATION | SUPHNTVI_F_TRUSTED_INSTALLER_OWNER;
    else if (supHardViUniStrPathStartsWithUniStr(&uBuf.UniStr, &g_SupLibHardenedAppBinNtPath.UniStr, true /*fCheckSlash*/))
        fFlags |= SUPHNTVI_F_REQUIRE_KERNEL_CODE_SIGNING | SUPHNTVI_F_REQUIRE_SIGNATURE_ENFORCEMENT;
# ifdef VBOX_PERMIT_MORE
    else if (supHardViIsAppPatchDir(uBuf.UniStr.Buffer, uBuf.UniStr.Length / sizeof(WCHAR)))
        fFlags |= SUPHNTVI_F_ALLOW_CAT_FILE_VERIFICATION | SUPHNTVI_F_TRUSTED_INSTALLER_OWNER;
    else if (supHardViUniStrPathStartsWithUniStr(&uBuf.UniStr, &g_ProgramFilesNtPath.UniStr, true /*fCheckSlash*/))
        fFlags |= SUPHNTVI_F_ALLOW_CAT_FILE_VERIFICATION | SUPHNTVI_F_TRUSTED_INSTALLER_OWNER;
    else if (supHardViUniStrPathStartsWithUniStr(&uBuf.UniStr, &g_CommonFilesNtPath.UniStr, true /*fCheckSlash*/))
        fFlags |= SUPHNTVI_F_ALLOW_CAT_FILE_VERIFICATION | SUPHNTVI_F_TRUSTED_INSTALLER_OWNER;
#  ifdef RT_ARCH_AMD64
    else if (supHardViUniStrPathStartsWithUniStr(&uBuf.UniStr, &g_ProgramFilesX86NtPath.UniStr, true /*fCheckSlash*/))
        fFlags |= SUPHNTVI_F_ALLOW_CAT_FILE_VERIFICATION | SUPHNTVI_F_TRUSTED_INSTALLER_OWNER;
    else if (supHardViUniStrPathStartsWithUniStr(&uBuf.UniStr, &g_CommonFilesX86NtPath.UniStr, true /*fCheckSlash*/))
        fFlags |= SUPHNTVI_F_ALLOW_CAT_FILE_VERIFICATION | SUPHNTVI_F_TRUSTED_INSTALLER_OWNER;
#  endif
# endif
# ifdef VBOX_PERMIT_VISUAL_STUDIO_PROFILING
    /* Hack to allow profiling our code with Visual Studio. */
    else if (   uBuf.UniStr.Length > sizeof(L"\\SamplingRuntime.dll")
             && memcmp(uBuf.UniStr.Buffer + (uBuf.UniStr.Length - sizeof(L"\\SamplingRuntime.dll") + sizeof(WCHAR)) / sizeof(WCHAR),
                       L"\\SamplingRuntime.dll", sizeof(L"\\SamplingRuntime.dll") - sizeof(WCHAR)) == 0 )
    {
        if (hMyFile != hFile)
            NtClose(hMyFile);
        *pfCallRealApi = true;
        return STATUS_SUCCESS;
    }
# endif
    else
    {
        supR3HardenedError(VINF_SUCCESS, false,
                           "supR3HardenedScreenImage/%s: Not a trusted location: '%ls' (fImage=%d fProtect=%#x fAccess=%#x)\n",
                            pszCaller, uBuf.UniStr.Buffer, fImage, *pfAccess, *pfProtect);
        if (hMyFile != hFile)
            NtClose(hMyFile);
        return STATUS_TRUST_FAILURE;
    }

#else  /* VBOX_PERMIT_EVEN_MORE */
    /*
     * Require trusted installer + some kind of signature on everything, except
     * for the VBox bits where we require kernel code signing and special
     * integrity checks.
     */
    uint32_t fFlags = 0;
    if (supHardViUniStrPathStartsWithUniStr(&uBuf.UniStr, &g_SupLibHardenedAppBinNtPath.UniStr, true /*fCheckSlash*/))
        fFlags |= SUPHNTVI_F_REQUIRE_KERNEL_CODE_SIGNING | SUPHNTVI_F_REQUIRE_SIGNATURE_ENFORCEMENT;
    else
        fFlags |= SUPHNTVI_F_ALLOW_CAT_FILE_VERIFICATION | SUPHNTVI_F_TRUSTED_INSTALLER_OWNER;
#endif /* VBOX_PERMIT_EVEN_MORE */

    /*
     * Do the verification. For better error message we borrow what's
     * left of the path buffer for an RTERRINFO buffer.
     */
    if (fIgnoreArch)
        fFlags |= SUPHNTVI_F_IGNORE_ARCHITECTURE;
    RTERRINFO ErrInfo;
    RTErrInfoInit(&ErrInfo, (char *)&uBuf.abBuffer[cbNameBuf], sizeof(uBuf) - cbNameBuf);

    int  rc;
    bool fWinVerifyTrust = false;
    rc = supHardenedWinVerifyImageByHandle(hMyFile, uBuf.UniStr.Buffer, fFlags, fAvoidWinVerifyTrust, &fWinVerifyTrust, &ErrInfo);
    if (RT_FAILURE(rc))
    {
        supR3HardenedError(VINF_SUCCESS, false,
                           "supR3HardenedScreenImage/%s: rc=%Rrc fImage=%d fProtect=%#x fAccess=%#x %ls: %s\n",
                           pszCaller, rc, fImage, *pfAccess, *pfProtect, uBuf.UniStr.Buffer, ErrInfo.pszMsg);
        if (hMyFile != hFile)
            supR3HardenedWinVerifyCacheInsert(&uBuf.UniStr, hMyFile, rc, fWinVerifyTrust, fFlags);
        return supR3HardenedScreenImageCalcStatus(rc);
    }

    /*
     * Insert into the cache.
     */
    if (hMyFile != hFile)
        supR3HardenedWinVerifyCacheInsert(&uBuf.UniStr, hMyFile, rc, fWinVerifyTrust, fFlags);

    *pfCallRealApi = true;
    return STATUS_SUCCESS;
}


/**
 * Preloads a file into the verify cache if possible.
 *
 * This is used to avoid known cyclic LoadLibrary issues with WinVerifyTrust.
 *
 * @param   pwszName            The name of the DLL to verify.
 */
DECLHIDDEN(void) supR3HardenedWinVerifyCachePreload(PCRTUTF16 pwszName)
{
    HANDLE              hFile = RTNT_INVALID_HANDLE_VALUE;
    IO_STATUS_BLOCK     Ios   = RTNT_IO_STATUS_BLOCK_INITIALIZER;

    UNICODE_STRING      UniStr;
    UniStr.Buffer = (PWCHAR)pwszName;
    UniStr.Length = (USHORT)(RTUtf16Len(pwszName) * sizeof(WCHAR));
    UniStr.MaximumLength = UniStr.Length + sizeof(WCHAR);

    OBJECT_ATTRIBUTES   ObjAttr;
    InitializeObjectAttributes(&ObjAttr, &UniStr, OBJ_CASE_INSENSITIVE, NULL /*hRootDir*/, NULL /*pSecDesc*/);

    NTSTATUS rcNt = NtCreateFile(&hFile,
                                 FILE_READ_DATA | READ_CONTROL | SYNCHRONIZE,
                                 &ObjAttr,
                                 &Ios,
                                 NULL /* Allocation Size*/,
                                 FILE_ATTRIBUTE_NORMAL,
                                 FILE_SHARE_READ,
                                 FILE_OPEN,
                                 FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT,
                                 NULL /*EaBuffer*/,
                                 0 /*EaLength*/);
    if (NT_SUCCESS(rcNt))
        rcNt = Ios.Status;
    if (!NT_SUCCESS(rcNt))
    {
        SUP_DPRINTF(("supR3HardenedWinVerifyCachePreload: Error %#x opening '%ls'.\n", rcNt, pwszName));
        return;
    }

    ULONG fAccess = 0;
    ULONG fProtect = 0;
    bool  fCallRealApi;
    //SUP_DPRINTF(("supR3HardenedWinVerifyCachePreload: scanning %ls\n", pwszName));
    supR3HardenedScreenImage(hFile, false, false /*fIgnoreArch*/, &fAccess, &fProtect, &fCallRealApi, "preload",
                             false /*fAvoidWinVerifyTrust*/, NULL /*pfQuiet*/);
    //SUP_DPRINTF(("supR3HardenedWinVerifyCachePreload: done %ls\n", pwszName));

    NtClose(hFile);
}



/**
 * Hook that monitors NtCreateSection calls.
 *
 * @returns NT status code.
 * @param   phSection           Where to return the section handle.
 * @param   fAccess             The desired access.
 * @param   pObjAttribs         The object attributes (optional).
 * @param   pcbSection          The section size (optional).
 * @param   fProtect            The max section protection.
 * @param   fAttribs            The section attributes.
 * @param   hFile               The file to create a section from (optional).
 */
static NTSTATUS NTAPI
supR3HardenedMonitor_NtCreateSection(PHANDLE phSection, ACCESS_MASK fAccess, POBJECT_ATTRIBUTES pObjAttribs,
                                     PLARGE_INTEGER pcbSection, ULONG fProtect, ULONG fAttribs, HANDLE hFile)
{
    bool fNeedUncChecking = false;
    if (   hFile != NULL
        && hFile != INVALID_HANDLE_VALUE)
    {
        bool const fImage    = RT_BOOL(fAttribs & (SEC_IMAGE | SEC_PROTECTED_IMAGE));
        bool const fExecMap  = RT_BOOL(fAccess & SECTION_MAP_EXECUTE);
        bool const fExecProt = RT_BOOL(fProtect & (PAGE_EXECUTE | PAGE_EXECUTE_READ | PAGE_EXECUTE_WRITECOPY
                                                   | PAGE_EXECUTE_READWRITE));
        if (fImage || fExecMap || fExecProt)
        {
            fNeedUncChecking = true;
            DWORD dwSavedLastError = RtlGetLastWin32Error();

            bool fCallRealApi;
            //SUP_DPRINTF(("supR3HardenedMonitor_NtCreateSection: 1\n"));
            NTSTATUS rcNt = supR3HardenedScreenImage(hFile, fImage, true /*fIgnoreArch*/, &fAccess, &fProtect, &fCallRealApi,
                                                     "NtCreateSection", true /*fAvoidWinVerifyTrust*/, NULL /*pfQuiet*/);
            //SUP_DPRINTF(("supR3HardenedMonitor_NtCreateSection: 2 rcNt=%#x fCallRealApi=%#x\n", rcNt, fCallRealApi));

            RtlRestoreLastWin32Error(dwSavedLastError);

            if (!NT_SUCCESS(rcNt))
                return rcNt;
            Assert(fCallRealApi);
            if (!fCallRealApi)
                return STATUS_TRUST_FAILURE;

        }
    }

    /*
     * Call checked out OK, call the original.
     */
    NTSTATUS rcNtReal = g_pfnNtCreateSectionReal(phSection, fAccess, pObjAttribs, pcbSection, fProtect, fAttribs, hFile);

    /*
     * Check that the image that got mapped bear some resemblance to the one that was
     * requested.  Apparently there are ways to trick the NT cache manager to map a
     * file different from hFile into memory using local UNC accesses.
     */
    if (   NT_SUCCESS(rcNtReal)
        && fNeedUncChecking)
    {
        DWORD dwSavedLastError = RtlGetLastWin32Error();

        bool fOkay = false;

        /* To get the name of the file backing the section, we unfortunately have to map it. */
        SIZE_T   cbView   = 0;
        PVOID    pvTmpMap = NULL;
        NTSTATUS rcNt = NtMapViewOfSection(*phSection, NtCurrentProcess(), &pvTmpMap, 0, 0, NULL /*poffSection*/, &cbView,
                                           ViewUnmap, MEM_TOP_DOWN, PAGE_EXECUTE);
        if (NT_SUCCESS(rcNt))
        {
            /* Query the name. */
            union
            {
                UNICODE_STRING  UniStr;
                RTUTF16         awcBuf[512];
            } uBuf;
            RT_ZERO(uBuf);
            SIZE_T   cbActual = 0;
            NTSTATUS rcNtQuery = NtQueryVirtualMemory(NtCurrentProcess(), pvTmpMap, MemorySectionName,
                                                      &uBuf, sizeof(uBuf) - sizeof(RTUTF16), &cbActual);

            /* Unmap the view. */
            rcNt = NtUnmapViewOfSection(NtCurrentProcess(), pvTmpMap);
            if (!NT_SUCCESS(rcNt))
                SUP_DPRINTF(("supR3HardenedMonitor_NtCreateSection: NtUnmapViewOfSection failed on %p (hSection=%p, hFile=%p) with %#x!\n",
                             pvTmpMap, *phSection, hFile, rcNt));

            /* Process the name query result. */
            if (NT_SUCCESS(rcNtQuery))
            {
                static UNICODE_STRING const s_UncPrefix = RTNT_CONSTANT_UNISTR(L"\\Device\\Mup");
                if (!supHardViUniStrPathStartsWithUniStr(&uBuf.UniStr, &s_UncPrefix, true /*fCheckSlash*/))
                    fOkay = true;
                else
                    supR3HardenedError(VINF_SUCCESS, false,
                                       "supR3HardenedMonitor_NtCreateSection: Image section with UNC path is not trusted: '%.*ls'\n",
                                       uBuf.UniStr.Length / sizeof(RTUTF16), uBuf.UniStr.Buffer);
            }
            else
                SUP_DPRINTF(("supR3HardenedMonitor_NtCreateSection: NtQueryVirtualMemory failed on %p (hFile=%p) with %#x -> STATUS_TRUST_FAILURE\n",
                             *phSection, hFile, rcNt));
        }
        else
            SUP_DPRINTF(("supR3HardenedMonitor_NtCreateSection: NtMapViewOfSection failed on %p (hFile=%p) with %#x -> STATUS_TRUST_FAILURE\n",
                         *phSection, hFile, rcNt));
        if (!fOkay)
        {
            NtClose(*phSection);
            *phSection = INVALID_HANDLE_VALUE;
            RtlRestoreLastWin32Error(dwSavedLastError);
            return STATUS_TRUST_FAILURE;
        }

        RtlRestoreLastWin32Error(dwSavedLastError);
    }
    return rcNtReal;
}


/**
 * Checks if the given name is a valid ApiSet name.
 *
 * This is only called on likely looking names.
 *
 * @returns true if ApiSet name, false if not.
 * @param   pName               The name to check out.
 */
static bool supR3HardenedIsApiSetDll(PUNICODE_STRING pName)
{
    /*
     * API added in Windows 8, or so they say.
     */
    if (ApiSetQueryApiSetPresence != NULL)
    {
        BOOLEAN fPresent = FALSE;
        NTSTATUS rcNt = ApiSetQueryApiSetPresence(pName, &fPresent);
        SUP_DPRINTF(("supR3HardenedIsApiSetDll: ApiSetQueryApiSetPresence(%.*ls) -> %#x, fPresent=%d\n",
                     pName->Length / sizeof(WCHAR), pName->Buffer, rcNt, fPresent));
        return fPresent != 0;
    }

    /*
     * Fallback needed for Windows 7.  Fortunately, there aren't too many fake DLLs here.
     */
    if (   g_uNtVerCombined >= SUP_NT_VER_W70
        && (   supHardViUtf16PathStartsWithEx(pName->Buffer, pName->Length / sizeof(WCHAR),
                                              L"api-ms-win-", 11, false /*fCheckSlash*/)
            || supHardViUtf16PathStartsWithEx(pName->Buffer, pName->Length / sizeof(WCHAR),
                                              L"ext-ms-win-", 11, false /*fCheckSlash*/) ))
    {
#define MY_ENTRY(a) { a, sizeof(a) - 1 }
        static const struct { const char *psz; size_t cch; } s_aKnownSets[] =
        {
            MY_ENTRY("api-ms-win-core-console-l1-1-0 "),
            MY_ENTRY("api-ms-win-core-datetime-l1-1-0"),
            MY_ENTRY("api-ms-win-core-debug-l1-1-0"),
            MY_ENTRY("api-ms-win-core-delayload-l1-1-0"),
            MY_ENTRY("api-ms-win-core-errorhandling-l1-1-0"),
            MY_ENTRY("api-ms-win-core-fibers-l1-1-0"),
            MY_ENTRY("api-ms-win-core-file-l1-1-0"),
            MY_ENTRY("api-ms-win-core-handle-l1-1-0"),
            MY_ENTRY("api-ms-win-core-heap-l1-1-0"),
            MY_ENTRY("api-ms-win-core-interlocked-l1-1-0"),
            MY_ENTRY("api-ms-win-core-io-l1-1-0"),
            MY_ENTRY("api-ms-win-core-libraryloader-l1-1-0"),
            MY_ENTRY("api-ms-win-core-localization-l1-1-0"),
            MY_ENTRY("api-ms-win-core-localregistry-l1-1-0"),
            MY_ENTRY("api-ms-win-core-memory-l1-1-0"),
            MY_ENTRY("api-ms-win-core-misc-l1-1-0"),
            MY_ENTRY("api-ms-win-core-namedpipe-l1-1-0"),
            MY_ENTRY("api-ms-win-core-processenvironment-l1-1-0"),
            MY_ENTRY("api-ms-win-core-processthreads-l1-1-0"),
            MY_ENTRY("api-ms-win-core-profile-l1-1-0"),
            MY_ENTRY("api-ms-win-core-rtlsupport-l1-1-0"),
            MY_ENTRY("api-ms-win-core-string-l1-1-0"),
            MY_ENTRY("api-ms-win-core-synch-l1-1-0"),
            MY_ENTRY("api-ms-win-core-sysinfo-l1-1-0"),
            MY_ENTRY("api-ms-win-core-threadpool-l1-1-0"),
            MY_ENTRY("api-ms-win-core-ums-l1-1-0"),
            MY_ENTRY("api-ms-win-core-util-l1-1-0"),
            MY_ENTRY("api-ms-win-core-xstate-l1-1-0"),
            MY_ENTRY("api-ms-win-security-base-l1-1-0"),
            MY_ENTRY("api-ms-win-security-lsalookup-l1-1-0"),
            MY_ENTRY("api-ms-win-security-sddl-l1-1-0"),
            MY_ENTRY("api-ms-win-service-core-l1-1-0"),
            MY_ENTRY("api-ms-win-service-management-l1-1-0"),
            MY_ENTRY("api-ms-win-service-management-l2-1-0"),
            MY_ENTRY("api-ms-win-service-winsvc-l1-1-0"),
        };
#undef MY_ENTRY

        /* drop the dll suffix if present. */
        PCRTUTF16 pawcName = pName->Buffer;
        size_t    cwcName  = pName->Length / sizeof(WCHAR);
        if (   cwcName > 5
            && (pawcName[cwcName - 1] == 'l' || pawcName[cwcName - 1] == 'L')
            && (pawcName[cwcName - 2] == 'l' || pawcName[cwcName - 2] == 'L')
            && (pawcName[cwcName - 3] == 'd' || pawcName[cwcName - 3] == 'D')
            &&  pawcName[cwcName - 4] == '.')
            cwcName -= 4;

        /* Search the table. */
        for (size_t i = 0; i < RT_ELEMENTS(s_aKnownSets); i++)
            if (   cwcName == s_aKnownSets[i].cch
                && RTUtf16NICmpAscii(pawcName, s_aKnownSets[i].psz, cwcName) == 0)
            {
                SUP_DPRINTF(("supR3HardenedIsApiSetDll: '%.*ls' -> true\n", pName->Length / sizeof(WCHAR)));
                return true;
            }

        SUP_DPRINTF(("supR3HardenedIsApiSetDll: Warning! '%.*ls' looks like an API set, but it's not in the list!\n",
                     pName->Length / sizeof(WCHAR), pName->Buffer));
    }

    SUP_DPRINTF(("supR3HardenedIsApiSetDll: '%.*ls' -> false\n", pName->Length / sizeof(WCHAR)));
    return false;
}


/**
 * Checks whether the given unicode string contains a path separator and at
 * least one dash.
 *
 * This is used to check for likely ApiSet name.  So far, all the pseudo DLL
 * names include multiple dashes, so we use that as a criteria for recognizing
 * them.  By happy coincident, most regular DLLs doesn't include dashes.
 *
 * @returns true if it contains path separator, false if only a name.
 * @param   pPath               The path to check.
 */
static bool supR3HardenedHasDashButNoPath(PUNICODE_STRING pPath)
{
    size_t    cDashes = 0;
    size_t    cwcLeft = pPath->Length / sizeof(WCHAR);
    PCRTUTF16 pwc     = pPath->Buffer;
    while (cwcLeft-- > 0)
    {
        RTUTF16 wc = *pwc++;
        switch (wc)
        {
            default:
                break;

            case '-':
                cDashes++;
                break;

            case '\\':
            case '/':
            case ':':
                return false;
        }
    }
    return cDashes > 0;
}


/**
 * Helper for supR3HardenedMonitor_LdrLoadDll.
 *
 * @returns NT status code.
 * @param   pwszPath        The path destination buffer.
 * @param   cwcPath         The size of the path buffer.
 * @param   pUniStrResult   The result string.
 * @param   pOrgName        The orignal name (for errors).
 * @param   pcwc            Where to return the actual length.
 */
static NTSTATUS supR3HardenedCopyRedirectionResult(WCHAR *pwszPath, size_t cwcPath, PUNICODE_STRING pUniStrResult,
                                                   PUNICODE_STRING pOrgName, UINT *pcwc)
{
    UINT cwc;
    *pcwc = cwc = pUniStrResult->Length / sizeof(WCHAR);
    if (pUniStrResult->Buffer == pwszPath)
        pwszPath[cwc] = '\0';
    else
    {
        if (cwc > cwcPath - 1)
        {
            supR3HardenedError(VINF_SUCCESS, false,
                               "supR3HardenedMonitor_LdrLoadDll: Name too long: %.*ls -> %.*ls (RtlDosApplyFileIoslationRedirection_Ustr)\n",
                               pOrgName->Length / sizeof(WCHAR), pOrgName->Buffer,
                               pUniStrResult->Length / sizeof(WCHAR), pUniStrResult->Buffer);
            return STATUS_NAME_TOO_LONG;
        }
        memcpy(&pwszPath[0], pUniStrResult->Buffer, pUniStrResult->Length);
        pwszPath[cwc] = '\0';
    }
    return STATUS_SUCCESS;
}


/**
 * Helper for supR3HardenedMonitor_LdrLoadDll that compares the name part of the
 * input path against a ASCII name string of a given length.
 *
 * @returns true if the name part matches
 * @param   pPath               The LdrLoadDll input path.
 * @param   pszName             The name to try match it with.
 * @param   cchName             The name length.
 */
static bool supR3HardenedIsFilenameMatchDll(PUNICODE_STRING pPath, const char *pszName, size_t cchName)
{
    if (pPath->Length < cchName * 2)
        return false;
    PCRTUTF16 pwszTmp = &pPath->Buffer[pPath->Length / sizeof(RTUTF16) - cchName];
    if (   pPath->Length != cchName
        && pwszTmp[-1] != '\\'
        && pwszTmp[-1] != '/')
        return false;
    return RTUtf16ICmpAscii(pwszTmp, pszName) == 0;
}


/**
 * Hooks that intercepts LdrLoadDll calls.
 *
 * Two purposes:
 *      -# Enforce our own search path restrictions.
 *      -# Prevalidate DLLs about to be loaded so we don't upset the loader data
 *         by doing it from within the NtCreateSection hook (WinVerifyTrust
 *         seems to be doing harm there on W7/32).
 *
 * @returns
 * @param   pwszSearchPath      The search path to use.
 * @param   pfFlags             Flags on input. DLL characteristics or something
 *                              on return?
 * @param   pName               The name of the module.
 * @param   phMod               Where the handle of the loaded DLL is to be
 *                              returned to the caller.
 */
static NTSTATUS NTAPI
supR3HardenedMonitor_LdrLoadDll(PWSTR pwszSearchPath, PULONG pfFlags, PUNICODE_STRING pName, PHANDLE phMod)
{
    DWORD                   dwSavedLastError = RtlGetLastWin32Error();
    PUNICODE_STRING const   pOrgName = pName;
    NTSTATUS                rcNt;

    /*
     * Make sure the DLL notification callback is registered.  If we could, we
     * would've done this during early process init, but due to lack of heap
     * and uninitialized loader lock, it's not possible that early on.
     *
     * The callback protects our NtDll hooks from getting unhooked by
     * "friendly" fire from the AV crowd.
     */
    supR3HardenedWinRegisterDllNotificationCallback();

    /*
     * Process WinVerifyTrust todo before and after.
     */
    supR3HardenedWinVerifyCacheProcessWvtTodos();

    /*
     * Reject things we don't want to deal with.
     */
    if (!pName || pName->Length == 0)
    {
        supR3HardenedError(VINF_SUCCESS, false, "supR3HardenedMonitor_LdrLoadDll: name is NULL or have a zero length.\n");
        SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: returns rcNt=%#x (pName=%p)\n", STATUS_INVALID_PARAMETER, pName));
        RtlRestoreLastWin32Error(dwSavedLastError);
        return STATUS_INVALID_PARAMETER;
    }
    PCWCHAR const  pawcOrgName = pName->Buffer;
    uint32_t const cwcOrgName  = pName->Length / sizeof(WCHAR);

    /*SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: pName=%.*ls *pfFlags=%#x pwszSearchPath=%p:%ls\n",
                 (unsigned)pName->Length / sizeof(WCHAR), pName->Buffer, pfFlags ? *pfFlags : UINT32_MAX, pwszSearchPath,
                 !((uintptr_t)pwszSearchPath & 1) && (uintptr_t)pwszSearchPath >= 0x2000U ? pwszSearchPath : L"<flags>"));*/

    /*
     * Reject long paths that's close to the 260 limit without looking.
     */
    if (cwcOrgName > 256)
    {
        supR3HardenedError(VINF_SUCCESS, false, "supR3HardenedMonitor_LdrLoadDll: too long name: %#x bytes\n", pName->Length);
        SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: returns rcNt=%#x\n", STATUS_NAME_TOO_LONG));
        RtlRestoreLastWin32Error(dwSavedLastError);
        return STATUS_NAME_TOO_LONG;
    }

    /*
     * Reject all UNC-like paths as we cannot trust non-local files at all.
     * Note! We may have to relax this to deal with long path specifications and NT pass thrus.
     */
    if (   cwcOrgName >= 3
        && RTPATH_IS_SLASH(pawcOrgName[0])
        && RTPATH_IS_SLASH(pawcOrgName[1])
        && !RTPATH_IS_SLASH(pawcOrgName[2]))
    {
        supR3HardenedError(VINF_SUCCESS, false, "supR3HardenedMonitor_LdrLoadDll: rejecting UNC name '%.*ls'\n", cwcOrgName, pawcOrgName);
        SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: returns rcNt=%#x\n", STATUS_REDIRECTOR_NOT_STARTED));
        RtlRestoreLastWin32Error(dwSavedLastError);
        return STATUS_REDIRECTOR_NOT_STARTED;
    }

    /*
     * Reject PGHook.dll as it creates a thread from its DllMain that breaks
     * our preconditions respawning the 2nd process, resulting in
     * VERR_SUP_VP_THREAD_NOT_ALONE.   The DLL is being loaded by a user APC
     * scheduled during kernel32.dll load notification from a kernel driver,
     * so failing the load attempt should not upset anyone.
     */
    if (g_enmSupR3HardenedMainState == SUPR3HARDENEDMAINSTATE_WIN_EARLY_STUB_DEVICE_OPENED)
    {
        static const struct { const char *psz; size_t cch; } s_aUnwantedEarlyDlls[] =
        {
            { RT_STR_TUPLE("PGHook.dll") },
        };
        for (unsigned i = 0; i < RT_ELEMENTS(s_aUnwantedEarlyDlls); i++)
            if (supR3HardenedIsFilenameMatchDll(pName, s_aUnwantedEarlyDlls[i].psz, s_aUnwantedEarlyDlls[i].cch))
            {
                SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: Refusing to load '%.*ls' as it is expected to create undesirable threads that will upset our respawn checks (returning STATUS_TOO_MANY_THREADS)\n",
                             pName->Length / sizeof(RTUTF16), pName->Buffer));
                return STATUS_TOO_MANY_THREADS;
            }
    }

    /*
     * Resolve the path, copying the result into wszPath
     */
    NTSTATUS        rcNtResolve     = STATUS_SUCCESS;
    bool            fSkipValidation = false;
    bool            fCheckIfLoaded  = false;
    WCHAR           wszPath[260];
    static UNICODE_STRING const s_DefaultSuffix = RTNT_CONSTANT_UNISTR(L".dll");
    UNICODE_STRING  UniStrStatic   = { 0, (USHORT)sizeof(wszPath) - sizeof(WCHAR), wszPath };
    UNICODE_STRING  UniStrDynamic  = { 0, 0, NULL };
    PUNICODE_STRING pUniStrResult  = NULL;
    UNICODE_STRING  ResolvedName;

    /*
     * Process the name a little, checking if it needs a DLL suffix and is pathless.
     */
    uint32_t        offLastSlash = UINT32_MAX;
    uint32_t        offLastDot   = UINT32_MAX;
    for (uint32_t i = 0; i < cwcOrgName; i++)
        switch (pawcOrgName[i])
        {
            case '\\':
            case '/':
                offLastSlash = i;
                offLastDot = UINT32_MAX;
                break;
            case '.':
                offLastDot = i;
                break;
        }
    bool const fNeedDllSuffix = offLastDot == UINT32_MAX;
    //bool const fTrailingDot   = offLastDot == cwcOrgName - 1;

    /*
     * Absolute path?
     */
    if (   (   cwcOrgName >= 4
            && RT_C_IS_ALPHA(pawcOrgName[0])
            && pawcOrgName[1] == ':'
            && RTPATH_IS_SLASH(pawcOrgName[2]) )
        || (   cwcOrgName >= 1
            && RTPATH_IS_SLASH(pawcOrgName[0]) )
       )
    {
        rcNtResolve = RtlDosApplyFileIsolationRedirection_Ustr(1 /*fFlags*/,
                                                               pName,
                                                               (PUNICODE_STRING)&s_DefaultSuffix,
                                                               &UniStrStatic,
                                                               &UniStrDynamic,
                                                               &pUniStrResult,
                                                               NULL /*pNewFlags*/,
                                                               NULL /*pcbFilename*/,
                                                               NULL /*pcbNeeded*/);
        if (NT_SUCCESS(rcNtResolve))
        {
            UINT cwc;
            rcNt = supR3HardenedCopyRedirectionResult(wszPath, RT_ELEMENTS(wszPath), pUniStrResult, pName, &cwc);
            RtlFreeUnicodeString(&UniStrDynamic);
            if (!NT_SUCCESS(rcNt))
            {
                SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: returns rcNt=%#x\n", rcNt));
                RtlRestoreLastWin32Error(dwSavedLastError);
                return rcNt;
            }

            ResolvedName.Buffer = wszPath;
            ResolvedName.Length = (USHORT)(cwc * sizeof(WCHAR));
            ResolvedName.MaximumLength = ResolvedName.Length + sizeof(WCHAR);

            SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: '%.*ls' -> '%.*ls' [redir]\n",
                         (unsigned)pName->Length / sizeof(WCHAR), pName->Buffer,
                         ResolvedName.Length / sizeof(WCHAR), ResolvedName.Buffer, rcNt));
            pName = &ResolvedName;
        }
        else
        {
            /* Copy the path. */
            memcpy(wszPath, pawcOrgName, cwcOrgName * sizeof(WCHAR));
            if (!fNeedDllSuffix)
                wszPath[cwcOrgName] = '\0';
            else
            {
                if (cwcOrgName + 4 >= RT_ELEMENTS(wszPath))
                {
                    supR3HardenedError(VINF_SUCCESS, false,
                                       "supR3HardenedMonitor_LdrLoadDll: Name too long (abs): %.*ls\n", cwcOrgName, pawcOrgName);
                    SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: returns rcNt=%#x\n", STATUS_NAME_TOO_LONG));
                    RtlRestoreLastWin32Error(dwSavedLastError);
                    return STATUS_NAME_TOO_LONG;
                }
                memcpy(&wszPath[cwcOrgName], L".dll", 5 * sizeof(WCHAR));
            }
        }
    }
    /*
     * Not an absolute path.  Check if it's one of those special API set DLLs
     * or something we're known to use but should be taken from WinSxS.
     */
    else if (   supR3HardenedHasDashButNoPath(pName)
             && supR3HardenedIsApiSetDll(pName))
    {
        memcpy(wszPath, pName->Buffer, pName->Length);
        wszPath[pName->Length / sizeof(WCHAR)] = '\0';
        fSkipValidation = true;
    }
    /*
     * Not an absolute path or special API set.  There are two alternatives
     * now, either there is no path at all or there is a relative path.  We
     * will resolve it to an absolute path in either case, failing the call
     * if we can't.
     */
    else
    {
        /*
         * Reject relative paths for now as they might be breakout attempts.
         */
        if (offLastSlash != UINT32_MAX)
        {
            supR3HardenedError(VINF_SUCCESS, false,
                               "supR3HardenedMonitor_LdrLoadDll: relative name not permitted: %.*ls\n",
                               cwcOrgName, pawcOrgName);
            SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: returns rcNt=%#x\n", STATUS_OBJECT_NAME_INVALID));
            RtlRestoreLastWin32Error(dwSavedLastError);
            return STATUS_OBJECT_NAME_INVALID;
        }

        /*
         * Perform dll redirection to WinSxS such.  We using an undocumented
         * API here, which as always is a bit risky...  ASSUMES that the API
         * returns a full DOS path.
         */
        UINT cwc;
        rcNtResolve = RtlDosApplyFileIsolationRedirection_Ustr(1 /*fFlags*/,
                                                               pName,
                                                               (PUNICODE_STRING)&s_DefaultSuffix,
                                                               &UniStrStatic,
                                                               &UniStrDynamic,
                                                               &pUniStrResult,
                                                               NULL /*pNewFlags*/,
                                                               NULL /*pcbFilename*/,
                                                               NULL /*pcbNeeded*/);
        if (NT_SUCCESS(rcNtResolve))
        {
            rcNt = supR3HardenedCopyRedirectionResult(wszPath, RT_ELEMENTS(wszPath), pUniStrResult, pName, &cwc);
            RtlFreeUnicodeString(&UniStrDynamic);
            if (!NT_SUCCESS(rcNt))
            {
                SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: returns rcNt=%#x\n", rcNt));
                RtlRestoreLastWin32Error(dwSavedLastError);
                return rcNt;
            }
        }
        else
        {
            /*
             * Search for the DLL.  Only System32 is allowed as the target of
             * a search on the API level, all VBox calls will have full paths.
             * If the DLL is not in System32, we will resort to check if it's
             * refering to an already loaded DLL (fCheckIfLoaded).
             */
            AssertCompile(sizeof(g_System32WinPath.awcBuffer) <= sizeof(wszPath));
            cwc = g_System32WinPath.UniStr.Length / sizeof(RTUTF16); Assert(cwc > 2);
            if (cwc + 1 + cwcOrgName + fNeedDllSuffix * 4 >= RT_ELEMENTS(wszPath))
            {
                supR3HardenedError(VINF_SUCCESS, false,
                                   "supR3HardenedMonitor_LdrLoadDll: Name too long (system32): %.*ls\n", cwcOrgName, pawcOrgName);
                SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: returns rcNt=%#x\n", STATUS_NAME_TOO_LONG));
                RtlRestoreLastWin32Error(dwSavedLastError);
                return STATUS_NAME_TOO_LONG;
            }
            memcpy(wszPath, g_System32WinPath.UniStr.Buffer, cwc * sizeof(RTUTF16));
            wszPath[cwc++] = '\\';
            memcpy(&wszPath[cwc], pawcOrgName, cwcOrgName * sizeof(WCHAR));
            cwc += cwcOrgName;
            if (!fNeedDllSuffix)
                wszPath[cwc] = '\0';
            else
            {
                memcpy(&wszPath[cwc], L".dll", 5 * sizeof(WCHAR));
                cwc += 4;
            }
            fCheckIfLoaded = true;
        }

        ResolvedName.Buffer = wszPath;
        ResolvedName.Length = (USHORT)(cwc * sizeof(WCHAR));
        ResolvedName.MaximumLength = ResolvedName.Length + sizeof(WCHAR);
        pName = &ResolvedName;
    }

#ifndef IN_SUP_R3_STATIC
    /*
     * Reject blacklisted DLLs based on input name.
     */
    for (unsigned i = 0; g_aSupNtViBlacklistedDlls[i].psz != NULL; i++)
        if (supR3HardenedIsFilenameMatchDll(pName, g_aSupNtViBlacklistedDlls[i].psz, g_aSupNtViBlacklistedDlls[i].cch))
        {
            SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: Refusing to load blacklisted DLL: '%.*ls'\n",
                         pName->Length / sizeof(RTUTF16), pName->Buffer));
            RtlRestoreLastWin32Error(dwSavedLastError);
            return STATUS_TOO_MANY_THREADS;
        }
#endif

    bool fQuiet = false;
    if (!fSkipValidation)
    {
        /*
         * Try open the file.  If this fails, never mind, just pass it on to
         * the real API as we've replaced any searchable name with a full name
         * and the real API can come up with a fitting status code for it.
         */
        HANDLE          hRootDir;
        UNICODE_STRING  NtPathUniStr;
        int rc = RTNtPathFromWinUtf16Ex(&NtPathUniStr, &hRootDir, wszPath, RTSTR_MAX);
        if (RT_FAILURE(rc))
        {
            supR3HardenedError(rc, false,
                               "supR3HardenedMonitor_LdrLoadDll: RTNtPathFromWinUtf16Ex failed on '%ls': %Rrc\n", wszPath, rc);
            SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: returns rcNt=%#x\n", STATUS_OBJECT_NAME_INVALID));
            RtlRestoreLastWin32Error(dwSavedLastError);
            return STATUS_OBJECT_NAME_INVALID;
        }

        HANDLE              hFile = RTNT_INVALID_HANDLE_VALUE;
        IO_STATUS_BLOCK     Ios   = RTNT_IO_STATUS_BLOCK_INITIALIZER;
        OBJECT_ATTRIBUTES   ObjAttr;
        InitializeObjectAttributes(&ObjAttr, &NtPathUniStr, OBJ_CASE_INSENSITIVE, hRootDir, NULL /*pSecDesc*/);

        rcNt = NtCreateFile(&hFile,
                            FILE_READ_DATA | READ_CONTROL | SYNCHRONIZE,
                            &ObjAttr,
                            &Ios,
                            NULL /* Allocation Size*/,
                            FILE_ATTRIBUTE_NORMAL,
                            FILE_SHARE_READ,
                            FILE_OPEN,
                            FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT,
                            NULL /*EaBuffer*/,
                            0 /*EaLength*/);
        if (NT_SUCCESS(rcNt))
            rcNt = Ios.Status;
        if (NT_SUCCESS(rcNt))
        {
            ULONG fAccess = 0;
            ULONG fProtect = 0;
            bool  fCallRealApi = false;
            rcNt = supR3HardenedScreenImage(hFile, true /*fImage*/, RT_VALID_PTR(pfFlags) && (*pfFlags & 0x2) /*fIgnoreArch*/,
                                            &fAccess, &fProtect, &fCallRealApi,
                                            "LdrLoadDll", false /*fAvoidWinVerifyTrust*/, &fQuiet);
            NtClose(hFile);
            if (!NT_SUCCESS(rcNt))
            {
                if (!fQuiet)
                {
                    if (pOrgName != pName)
                        supR3HardenedError(VINF_SUCCESS, false, "supR3HardenedMonitor_LdrLoadDll: rejecting '%ls': rcNt=%#x\n",
                                           wszPath, rcNt);
                    else
                        supR3HardenedError(VINF_SUCCESS, false, "supR3HardenedMonitor_LdrLoadDll: rejecting '%ls' (%.*ls): rcNt=%#x\n",
                                           wszPath, pOrgName->Length / sizeof(WCHAR), pOrgName->Buffer, rcNt);
                    SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: returns rcNt=%#x '%ls'\n", rcNt, wszPath));
                }
                RtlRestoreLastWin32Error(dwSavedLastError);
                return rcNt;
            }

            supR3HardenedWinVerifyCacheProcessImportTodos();
        }
        else
        {
            DWORD dwErr = RtlGetLastWin32Error();

            /*
             * Deal with special case where the caller (first case was MS LifeCam)
             * is using LoadLibrary instead of GetModuleHandle to find a loaded DLL.
             */
            NTSTATUS rcNtGetDll = STATUS_SUCCESS;
            if (   fCheckIfLoaded
                 && (   rcNt == STATUS_OBJECT_NAME_NOT_FOUND
                     || rcNt == STATUS_OBJECT_PATH_NOT_FOUND))
            {
                rcNtGetDll = LdrGetDllHandle(NULL /*DllPath*/, NULL /*pfFlags*/, pOrgName, phMod);
                if (NT_SUCCESS(rcNtGetDll))
                {
                    RTNtPathFree(&NtPathUniStr, &hRootDir);
                    RtlRestoreLastWin32Error(dwSavedLastError);
                    return rcNtGetDll;
                }
            }

            SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: error opening '%ls': %u (NtPath=%.*ls; Input=%.*ls; rcNtGetDll=%#x\n",
                         wszPath, dwErr, NtPathUniStr.Length / sizeof(RTUTF16), NtPathUniStr.Buffer,
                         pOrgName->Length / sizeof(WCHAR), pOrgName->Buffer, rcNtGetDll));

            RTNtPathFree(&NtPathUniStr, &hRootDir);
            RtlRestoreLastWin32Error(dwSavedLastError);
            SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: returns rcNt=%#x '%ls'\n", rcNt, wszPath));
            return rcNt;
        }
        RTNtPathFree(&NtPathUniStr, &hRootDir);
    }

    /*
     * Screened successfully enough.  Call the real thing.
     */
    if (!fQuiet)
    {
        if (pOrgName != pName)
            SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: pName=%.*ls (Input=%.*ls, rcNtResolve=%#x) *pfFlags=%#x pwszSearchPath=%p:%ls [calling]\n",
                         (unsigned)pName->Length / sizeof(WCHAR), pName->Buffer,
                         (unsigned)pOrgName->Length / sizeof(WCHAR), pOrgName->Buffer, rcNtResolve,
                         pfFlags ? *pfFlags : UINT32_MAX, pwszSearchPath,
                         !((uintptr_t)pwszSearchPath & 1) && (uintptr_t)pwszSearchPath >= 0x2000U ? pwszSearchPath : L"<flags>"));
        else
            SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: pName=%.*ls (rcNtResolve=%#x) *pfFlags=%#x pwszSearchPath=%p:%ls [calling]\n",
                         (unsigned)pName->Length / sizeof(WCHAR), pName->Buffer, rcNtResolve,
                         pfFlags ? *pfFlags : UINT32_MAX, pwszSearchPath,
                         !((uintptr_t)pwszSearchPath & 1) && (uintptr_t)pwszSearchPath >= 0x2000U ? pwszSearchPath : L"<flags>"));
    }

    RtlRestoreLastWin32Error(dwSavedLastError);
    rcNt = g_pfnLdrLoadDllReal(pwszSearchPath, pfFlags, pName, phMod);

    /*
     * Log the result and process pending WinVerifyTrust work if we can.
     */
    dwSavedLastError = RtlGetLastWin32Error();

    if (NT_SUCCESS(rcNt) && phMod)
        SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: returns rcNt=%#x hMod=%p '%ls'\n", rcNt, *phMod, wszPath));
    else if (!NT_SUCCESS(rcNt) || !fQuiet)
        SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: returns rcNt=%#x '%ls'\n", rcNt, wszPath));

    supR3HardenedWinVerifyCacheProcessWvtTodos();

    RtlRestoreLastWin32Error(dwSavedLastError);

    return rcNt;
}


/**
 * DLL load and unload notification callback.
 *
 * This is a safety against our LdrLoadDll hook being replaced by protection
 * software.  Though, we prefer the LdrLoadDll hook to this one as it allows us
 * to call WinVerifyTrust more freely.
 *
 * @param   ulReason    The reason we're called, see
 *                      LDR_DLL_NOTIFICATION_REASON_XXX.
 * @param   pData       Reason specific data.  (Format is currently the same for
 *                      both load and unload.)
 * @param   pvUser      User parameter (ignored).
 *
 * @remarks Vista and later.
 * @remarks The loader lock is held when we're called, at least on Windows 7.
 */
static VOID CALLBACK
supR3HardenedDllNotificationCallback(ULONG ulReason, PCLDR_DLL_NOTIFICATION_DATA pData, PVOID pvUser) RT_NOTHROW_DEF
{
    NOREF(pvUser);

    /*
     * Screen the image on load.  We will normally get a verification cache
     * hit here because of the LdrLoadDll and NtCreateSection hooks, so it
     * should be relatively cheap to recheck.  In case our NtDll patches
     * got re
     *
     * This ASSUMES that we get informed after the fact as indicated by the
     * available documentation.
     */
    if (ulReason == LDR_DLL_NOTIFICATION_REASON_LOADED)
    {
        SUP_DPRINTF(("supR3HardenedDllNotificationCallback: load   %p LB %#010x %.*ls [fFlags=%#x]\n",
                     pData->Loaded.DllBase, pData->Loaded.SizeOfImage,
                     pData->Loaded.FullDllName->Length / sizeof(WCHAR), pData->Loaded.FullDllName->Buffer,
                     pData->Loaded.Flags));

        /* Convert the windows path to an NT path and open it. */
        HANDLE          hRootDir;
        UNICODE_STRING  NtPathUniStr;
        int rc = RTNtPathFromWinUtf16Ex(&NtPathUniStr, &hRootDir, pData->Loaded.FullDllName->Buffer,
                                        pData->Loaded.FullDllName->Length / sizeof(WCHAR));
        if (RT_FAILURE(rc))
        {
            supR3HardenedFatal("supR3HardenedDllNotificationCallback: RTNtPathFromWinUtf16Ex failed on '%.*ls': %Rrc\n",
                               pData->Loaded.FullDllName->Length / sizeof(WCHAR), pData->Loaded.FullDllName->Buffer, rc);
            return;
        }

        HANDLE              hFile = RTNT_INVALID_HANDLE_VALUE;
        IO_STATUS_BLOCK     Ios   = RTNT_IO_STATUS_BLOCK_INITIALIZER;
        OBJECT_ATTRIBUTES   ObjAttr;
        InitializeObjectAttributes(&ObjAttr, &NtPathUniStr, OBJ_CASE_INSENSITIVE, hRootDir, NULL /*pSecDesc*/);

        NTSTATUS rcNt = NtCreateFile(&hFile,
                                     FILE_READ_DATA | READ_CONTROL | SYNCHRONIZE,
                                     &ObjAttr,
                                     &Ios,
                                     NULL /* Allocation Size*/,
                                     FILE_ATTRIBUTE_NORMAL,
                                     FILE_SHARE_READ,
                                     FILE_OPEN,
                                     FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT,
                                     NULL /*EaBuffer*/,
                                     0 /*EaLength*/);
        if (NT_SUCCESS(rcNt))
            rcNt = Ios.Status;
        if (!NT_SUCCESS(rcNt))
        {
            supR3HardenedFatal("supR3HardenedDllNotificationCallback: NtCreateFile failed on '%.*ls' / '%.*ls': %#x\n",
                               pData->Loaded.FullDllName->Length / sizeof(WCHAR), pData->Loaded.FullDllName->Buffer,
                               NtPathUniStr.Length / sizeof(WCHAR), NtPathUniStr.Buffer, rcNt);
            /* not reached */
        }

        /* Do the screening. */
        ULONG fAccess = 0;
        ULONG fProtect = 0;
        bool  fCallRealApi = false;
        bool  fQuietFailure = false;
        rcNt = supR3HardenedScreenImage(hFile, true /*fImage*/, true /*fIgnoreArch*/, &fAccess, &fProtect, &fCallRealApi,
                                        "LdrLoadDll", true /*fAvoidWinVerifyTrust*/, &fQuietFailure);
        NtClose(hFile);
        if (!NT_SUCCESS(rcNt))
        {
            supR3HardenedFatal("supR3HardenedDllNotificationCallback: supR3HardenedScreenImage failed on '%.*ls' / '%.*ls': %#x\n",
                               pData->Loaded.FullDllName->Length / sizeof(WCHAR), pData->Loaded.FullDllName->Buffer,
                               NtPathUniStr.Length / sizeof(WCHAR), NtPathUniStr.Buffer, rcNt);
            /* not reached */
        }
        RTNtPathFree(&NtPathUniStr, &hRootDir);
    }
    /*
     * Log the unload call.
     */
    else if (ulReason == LDR_DLL_NOTIFICATION_REASON_UNLOADED)
    {
        SUP_DPRINTF(("supR3HardenedDllNotificationCallback: Unload %p LB %#010x %.*ls [flags=%#x]\n",
                     pData->Unloaded.DllBase, pData->Unloaded.SizeOfImage,
                     pData->Unloaded.FullDllName->Length / sizeof(WCHAR), pData->Unloaded.FullDllName->Buffer,
                     pData->Unloaded.Flags));
    }
    /*
     * Just log things we don't know and then return without caching anything.
     */
    else
    {
        static uint32_t s_cLogEntries = 0;
        if (s_cLogEntries++ < 32)
            SUP_DPRINTF(("supR3HardenedDllNotificationCallback: ulReason=%u pData=%p\n", ulReason, pData));
        return;
    }

    /*
     * Use this opportunity to make sure our NtDll patches are still in place,
     * since they may be replaced by indecent protection software solutions.
     */
    supR3HardenedWinReInstallHooks(false /*fFirstCall */);
}


/**
 * Registers the DLL notification callback if it hasn't already been registered.
 */
static void supR3HardenedWinRegisterDllNotificationCallback(void)
{
    /*
     * The notification API was added in Vista, so it's an optional (weak) import.
     */
    if (   LdrRegisterDllNotification != NULL
        && g_cDllNotificationRegistered <= 0
        && g_cDllNotificationRegistered > -32)
    {
        NTSTATUS rcNt = LdrRegisterDllNotification(0, supR3HardenedDllNotificationCallback, NULL, &g_pvDllNotificationCookie);
        if (NT_SUCCESS(rcNt))
        {
            SUP_DPRINTF(("Registered Dll notification callback with NTDLL.\n"));
            g_cDllNotificationRegistered = 1;
        }
        else
        {
            supR3HardenedError(rcNt, false /*fFatal*/, "LdrRegisterDllNotification failed: %#x\n", rcNt);
            g_cDllNotificationRegistered--;
        }
    }
}


/**
 * Dummy replacement routine we use for passifying unwanted user APC
 * callbacks during early process initialization.
 *
 * @sa supR3HardenedMonitor_KiUserApcDispatcher_C
 */
static VOID NTAPI supR3HardenedWinDummyApcRoutine(PVOID pvArg1, PVOID pvArg2, PVOID pvArg3)
{
    SUP_DPRINTF(("supR3HardenedWinDummyApcRoutine: pvArg1=%p pvArg2=%p pvArg3=%p\n", pvArg1, pvArg2, pvArg3));
    RT_NOREF(pvArg1, pvArg2, pvArg3);
}


/**
 * This is called when ntdll!KiUserApcDispatcher is invoked (via
 * supR3HardenedMonitor_KiUserApcDispatcher).
 *
 * The parent process hooks KiUserApcDispatcher before the guest starts
 * executing. There should only be one APC request dispatched while the process
 * is being initialized, and that's the one calling ntdll!LdrInitializeThunk.
 *
 * @returns Where to go to run the original code.
 * @param   pvApcArgs   The APC dispatcher arguments.
 */
DECLASM(uintptr_t) supR3HardenedMonitor_KiUserApcDispatcher_C(void *pvApcArgs)
{
#ifdef RT_ARCH_AMD64
    PCONTEXT   pCtx        = (PCONTEXT)pvApcArgs;
    uintptr_t *ppfnRoutine = (uintptr_t *)&pCtx->P4Home;
#else
    struct X86APCCTX
    {
        uintptr_t   pfnRoutine;
        uintptr_t   pvCtx;
        uintptr_t   pvUser1;
        uintptr_t   pvUser2;
        CONTEXT     Ctx;
    } *pCtx = (struct X86APCCTX *)pvApcArgs;
    uintptr_t *ppfnRoutine = &pCtx->pfnRoutine;
#endif
    uintptr_t  pfnRoutine = *ppfnRoutine;

    if (g_enmSupR3HardenedMainState < SUPR3HARDENEDMAINSTATE_HARDENED_MAIN_CALLED)
    {
        if (pfnRoutine == g_pfnLdrInitializeThunk) /* Note! we could use this to detect thread creation too. */
            SUP_DPRINTF(("supR3HardenedMonitor_KiUserApcDispatcher_C: pfnRoutine=%p enmState=%d - okay\n",
                         pfnRoutine, g_enmSupR3HardenedMainState));
        else
        {
            *ppfnRoutine = (uintptr_t)supR3HardenedWinDummyApcRoutine;
            SUP_DPRINTF(("supR3HardenedMonitor_KiUserApcDispatcher_C: pfnRoutine=%p enmState=%d -> supR3HardenedWinDummyApcRoutine\n",
                         pfnRoutine, g_enmSupR3HardenedMainState));
        }
    }
    return (uintptr_t)g_pfnKiUserApcDispatcherReal;
}


/**
 * SUP_DPRINTF on pCtx, with lead-in text.
 */
static void supR3HardNtDprintCtx(PCONTEXT pCtx, const char *pszLeadIn)
{
#ifdef RT_ARCH_AMD64
    SUP_DPRINTF(("%s\n"
                 "  rax=%016RX64 rbx=%016RX64 rcx=%016RX64 rdx=%016RX64\n"
                 "  rsi=%016RX64 rdi=%016RX64 r8 =%016RX64 r9 =%016RX64\n"
                 "  r10=%016RX64 r11=%016RX64 r12=%016RX64 r13=%016RX64\n"
                 "  r14=%016RX64 r15=%016RX64  P1=%016RX64  P2=%016RX64\n"
                 "  rip=%016RX64 rsp=%016RX64 rbp=%016RX64    ctxflags=%08x\n"
                 "  cs=%04x ss=%04x ds=%04x es=%04x fs=%04x gs=%04x    eflags=%08x   mxcrx=%08x\n"
                 "   P3=%016RX64  P4=%016RX64  P5=%016RX64  P6=%016RX64\n"
                 "  dr0=%016RX64 dr1=%016RX64 dr2=%016RX64 dr3=%016RX64\n"
                 "  dr6=%016RX64 dr7=%016RX64 vcr=%016RX64 dcr=%016RX64\n"
                 "  lbt=%016RX64 lbf=%016RX64 lxt=%016RX64 lxf=%016RX64\n"
                 ,
                 pszLeadIn,
                 pCtx->Rax, pCtx->Rbx, pCtx->Rcx, pCtx->Rdx,
                 pCtx->Rsi, pCtx->Rdi, pCtx->R8, pCtx->R9,
                 pCtx->R10, pCtx->R11, pCtx->R12, pCtx->R13,
                 pCtx->R14, pCtx->R15, pCtx->P1Home, pCtx->P2Home,
                 pCtx->Rip, pCtx->Rsp, pCtx->Rbp, pCtx->ContextFlags,
                 pCtx->SegCs, pCtx->SegSs, pCtx->SegDs, pCtx->SegEs, pCtx->SegFs, pCtx->SegGs, pCtx->EFlags, pCtx->MxCsr,
                 pCtx->P3Home, pCtx->P4Home, pCtx->P5Home, pCtx->P6Home,
                 pCtx->Dr0, pCtx->Dr1, pCtx->Dr2, pCtx->Dr3,
                 pCtx->Dr6, pCtx->Dr7, pCtx->VectorControl, pCtx->DebugControl,
                 pCtx->LastBranchToRip, pCtx->LastBranchFromRip, pCtx->LastExceptionToRip, pCtx->LastExceptionFromRip ));
#elif defined(RT_ARCH_X86)
    SUP_DPRINTF(("%s\n"
                 "  eax=%08RX32 ebx=%08RX32 ecx=%08RX32 edx=%08RX32 esi=%08rx64 edi=%08RX32\n"
                 "  eip=%08RX32 esp=%08RX32 ebp=%08RX32 eflags=%08RX32\n"
                 "  cs=%04RX16 ds=%04RX16 es=%04RX16 fs=%04RX16 gs=%04RX16\n"
                 "  dr0=%08RX32 dr1=%08RX32 dr2=%08RX32 dr3=%08RX32 dr6=%08RX32 dr7=%08RX32\n",
                 pszLeadIn,
                 pCtx->Eax, pCtx->Ebx, pCtx->Ecx, pCtx->Edx, pCtx->Esi, pCtx->Edi,
                 pCtx->Eip, pCtx->Esp, pCtx->Ebp, pCtx->EFlags,
                 pCtx->SegCs, pCtx->SegDs, pCtx->SegEs, pCtx->SegFs, pCtx->SegGs,
                 pCtx->Dr0, pCtx->Dr1, pCtx->Dr2, pCtx->Dr3, pCtx->Dr6, pCtx->Dr7));
#else
# error "Unsupported arch."
#endif
    RT_NOREF(pCtx, pszLeadIn);
}


#ifndef VBOX_WITHOUT_HARDENDED_XCPT_LOGGING
/**
 * This is called when ntdll!KiUserExceptionDispatcher is invoked (via
 * supR3HardenedMonitor_KiUserExceptionDispatcher).
 *
 * For 64-bit processes there is a return and two parameters on the stack.
 *
 * @returns Where to go to run the original code.
 * @param   pXcptRec    The exception record.
 * @param   pCtx        The exception context.
 */
DECLASM(uintptr_t) supR3HardenedMonitor_KiUserExceptionDispatcher_C(PEXCEPTION_RECORD pXcptRec, PCONTEXT pCtx)
{
    /*
     * Ignore the guard page violation.
     */
    if (pXcptRec->ExceptionCode == STATUS_GUARD_PAGE_VIOLATION)
        return (uintptr_t)g_pfnKiUserExceptionDispatcherReal;

    /*
     * Log the exception and context.
     */
    char szLeadIn[384];
    if (pXcptRec->NumberParameters == 0)
        RTStrPrintf(szLeadIn, sizeof(szLeadIn), "KiUserExceptionDispatcher: %#x @ %p (flags=%#x)",
                    pXcptRec->ExceptionCode, pXcptRec->ExceptionAddress, pXcptRec->ExceptionFlags);
    else if (pXcptRec->NumberParameters == 1)
        RTStrPrintf(szLeadIn, sizeof(szLeadIn), "KiUserExceptionDispatcher: %#x (%p) @ %p (flags=%#x)",
                    pXcptRec->ExceptionCode, pXcptRec->ExceptionInformation[0],
                    pXcptRec->ExceptionAddress, pXcptRec->ExceptionFlags);
    else if (pXcptRec->NumberParameters == 2)
        RTStrPrintf(szLeadIn, sizeof(szLeadIn), "KiUserExceptionDispatcher: %#x (%p, %p) @ %p (flags=%#x)",
                    pXcptRec->ExceptionCode, pXcptRec->ExceptionInformation[0], pXcptRec->ExceptionInformation[1],
                    pXcptRec->ExceptionAddress, pXcptRec->ExceptionFlags);
    else if (pXcptRec->NumberParameters == 3)
        RTStrPrintf(szLeadIn, sizeof(szLeadIn), "KiUserExceptionDispatcher: %#x (%p, %p, %p) @ %p (flags=%#x)",
                    pXcptRec->ExceptionCode, pXcptRec->ExceptionInformation[0], pXcptRec->ExceptionInformation[1],
                    pXcptRec->ExceptionInformation[2], pXcptRec->ExceptionAddress, pXcptRec->ExceptionFlags);
    else
        RTStrPrintf(szLeadIn, sizeof(szLeadIn), "KiUserExceptionDispatcher: %#x (#%u: %p, %p, %p, %p, %p, %p, %p, %p, ...) @ %p (flags=%#x)",
                    pXcptRec->ExceptionCode, pXcptRec->NumberParameters,
                    pXcptRec->ExceptionInformation[0], pXcptRec->ExceptionInformation[1],
                    pXcptRec->ExceptionInformation[2], pXcptRec->ExceptionInformation[3],
                    pXcptRec->ExceptionInformation[4], pXcptRec->ExceptionInformation[5],
                    pXcptRec->ExceptionInformation[6], pXcptRec->ExceptionInformation[7],
                    pXcptRec->ExceptionAddress, pXcptRec->ExceptionFlags);
    supR3HardNtDprintCtx(pCtx, szLeadIn);

    return (uintptr_t)g_pfnKiUserExceptionDispatcherReal;
}
#endif /* !VBOX_WITHOUT_HARDENDED_XCPT_LOGGING */


static void supR3HardenedWinHookFailed(const char *pszWhich, uint8_t const *pbPrologue)
{
    supR3HardenedFatalMsg("supR3HardenedWinInstallHooks", kSupInitOp_Misc, VERR_NO_MEMORY,
                          "Failed to install %s monitor: %x %x %x %x  %x %x %x %x  %x %x %x %x  %x %x %x %x\n "
#ifdef RT_ARCH_X86
                          "(It is also possible you are running 32-bit VirtualBox under 64-bit windows.)\n"
#endif
                          ,
                          pszWhich,
                          pbPrologue[0],  pbPrologue[1],  pbPrologue[2],  pbPrologue[3],
                          pbPrologue[4],  pbPrologue[5],  pbPrologue[6],  pbPrologue[7],
                          pbPrologue[8],  pbPrologue[9],  pbPrologue[10], pbPrologue[11],
                          pbPrologue[12], pbPrologue[13], pbPrologue[14], pbPrologue[15]);
}


/**
 * IPRT thread that waits for the parent process to terminate and reacts by
 * exiting the current process.
 *
 * @returns VINF_SUCCESS
 * @param   hSelf               The current thread.  Ignored.
 * @param   pvUser              The handle of the parent process.
 */
static DECLCALLBACK(int) supR3HardenedWinParentWatcherThread(RTTHREAD hSelf, void *pvUser)
{
    HANDLE hProcWait = (HANDLE)pvUser;
    NOREF(hSelf);

    /*
     * Wait for the parent to terminate.
     */
    NTSTATUS rcNt;
    for (;;)
    {
        rcNt = NtWaitForSingleObject(hProcWait, TRUE /*Alertable*/, NULL /*pTimeout*/);
        if (   rcNt == STATUS_WAIT_0
            || rcNt == STATUS_ABANDONED_WAIT_0)
            break;
        if (   rcNt != STATUS_TIMEOUT
            && rcNt != STATUS_USER_APC
            && rcNt != STATUS_ALERTED)
            supR3HardenedFatal("NtWaitForSingleObject returned %#x\n", rcNt);
    }

    /*
     * Proxy the termination code of the child, if it exited already.
     */
    PROCESS_BASIC_INFORMATION BasicInfo;
    NTSTATUS rcNt2 = NtQueryInformationProcess(hProcWait, ProcessBasicInformation, &BasicInfo, sizeof(BasicInfo), NULL);
    if (   !NT_SUCCESS(rcNt2)
        || BasicInfo.ExitStatus == STATUS_PENDING)
        BasicInfo.ExitStatus = RTEXITCODE_FAILURE;

    NtClose(hProcWait);
    SUP_DPRINTF(("supR3HardenedWinParentWatcherThread: Quitting: ExitCode=%#x rcNt=%#x\n", BasicInfo.ExitStatus, rcNt));
    suplibHardenedExit((RTEXITCODE)BasicInfo.ExitStatus);
    /* not reached */
}


/**
 * Creates the parent watcher thread that will make sure this process exits when
 * the parent does.
 *
 * This is a necessary evil to make VBoxNetDhcp and VBoxNetNat termination from
 * Main work without too much new magic.  It also makes Ctrl-C or similar work
 * in on the hardened processes in the windows console.
 *
 * @param   hVBoxRT             The VBoxRT.dll handle.  We use RTThreadCreate to
 *                              spawn the thread to avoid duplicating thread
 *                              creation and thread naming code from IPRT.
 */
DECLHIDDEN(void) supR3HardenedWinCreateParentWatcherThread(HMODULE hVBoxRT)
{
    /*
     * Resolve runtime methods that we need.
     */
    PFNRTTHREADCREATE pfnRTThreadCreate = (PFNRTTHREADCREATE)GetProcAddress(hVBoxRT, "RTThreadCreate");
    SUPR3HARDENED_ASSERT(pfnRTThreadCreate != NULL);

    /*
     * Find the parent process ID.
     */
    PROCESS_BASIC_INFORMATION BasicInfo;
    NTSTATUS rcNt = NtQueryInformationProcess(NtCurrentProcess(), ProcessBasicInformation, &BasicInfo, sizeof(BasicInfo), NULL);
    if (!NT_SUCCESS(rcNt))
        supR3HardenedFatal("supR3HardenedWinCreateParentWatcherThread: NtQueryInformationProcess failed: %#x\n", rcNt);

    /*
     * Open the parent process for waiting and exitcode query.
     */
    OBJECT_ATTRIBUTES ObjAttr;
    InitializeObjectAttributes(&ObjAttr, NULL, 0, NULL /*hRootDir*/, NULL /*pSecDesc*/);

    CLIENT_ID ClientId;
    ClientId.UniqueProcess = (HANDLE)BasicInfo.InheritedFromUniqueProcessId;
    ClientId.UniqueThread  = NULL;

    HANDLE hParent;
    rcNt = NtOpenProcess(&hParent, SYNCHRONIZE | PROCESS_QUERY_INFORMATION, &ObjAttr, &ClientId);
    if (!NT_SUCCESS(rcNt))
        supR3HardenedFatalMsg("supR3HardenedWinCreateParentWatcherThread", kSupInitOp_Misc, VERR_GENERAL_FAILURE,
                              "NtOpenProcess(%p.0) failed: %#x\n", ClientId.UniqueProcess, rcNt);

    /*
     * Create the thread that should do the waiting.
     */
    int rc = pfnRTThreadCreate(NULL, supR3HardenedWinParentWatcherThread, hParent, _64K /* stack */,
                               RTTHREADTYPE_DEFAULT, 0 /*fFlags*/, "ParentWatcher");
    if (RT_FAILURE(rc))
        supR3HardenedFatal("supR3HardenedWinCreateParentWatcherThread: RTThreadCreate failed: %Rrc\n", rc);
}


/**
 * Checks if the calling thread is the only one in the process.
 *
 * @returns true if we're positive we're alone, false if not.
 */
static bool supR3HardenedWinAmIAlone(void) RT_NOTHROW_DEF
{
    ULONG    fAmIAlone = 0;
    ULONG    cbIgn     = 0;
    NTSTATUS rcNt = NtQueryInformationThread(NtCurrentThread(), ThreadAmILastThread, &fAmIAlone, sizeof(fAmIAlone), &cbIgn);
    Assert(NT_SUCCESS(rcNt));
    return NT_SUCCESS(rcNt) && fAmIAlone != 0;
}


/**
 * Simplify NtProtectVirtualMemory interface.
 *
 * Modifies protection for the current process.  Caller must know the current
 * protection as it's not returned.
 *
 * @returns NT status code.
 * @param   pvMem               The memory to change protection for.
 * @param   cbMem               The amount of memory to change.
 * @param   fNewProt            The new protection.
 */
static NTSTATUS supR3HardenedWinProtectMemory(PVOID pvMem, SIZE_T cbMem, ULONG fNewProt) RT_NOTHROW_DEF
{
    ULONG fOldProt = 0;
    return NtProtectVirtualMemory(NtCurrentProcess(), &pvMem, &cbMem, fNewProt, &fOldProt);
}


/**
 * Installs or reinstalls the NTDLL patches.
 */
static void supR3HardenedWinReInstallHooks(bool fFirstCall) RT_NOTHROW_DEF
{
    struct
    {
        size_t          cbPatch;
        uint8_t const  *pabPatch;
        uint8_t       **ppbApi;
        const char     *pszName;
    } const s_aPatches[] =
    {
        { sizeof(g_abNtCreateSectionPatch),           g_abNtCreateSectionPatch,           &g_pbNtCreateSection,           "NtCreateSection"     },
        { sizeof(g_abLdrLoadDllPatch),                g_abLdrLoadDllPatch,                &g_pbLdrLoadDll,                "LdrLoadDll"          },
        { sizeof(g_abKiUserApcDispatcherPatch),       g_abKiUserApcDispatcherPatch,       &g_pbKiUserApcDispatcher,       "KiUserApcDispatcher" },
#ifndef VBOX_WITHOUT_HARDENDED_XCPT_LOGGING
        { sizeof(g_abKiUserExceptionDispatcherPatch), g_abKiUserExceptionDispatcherPatch, &g_pbKiUserExceptionDispatcher, "KiUserExceptionDispatcher" },
#endif
    };

    ULONG fAmIAlone = ~(ULONG)0;

    for (uint32_t i = 0; i < RT_ELEMENTS(s_aPatches); i++)
    {
        uint8_t *pbApi = *s_aPatches[i].ppbApi;
        if (memcmp(pbApi, s_aPatches[i].pabPatch, s_aPatches[i].cbPatch) != 0)
        {
            /*
             * Log the incident if it's not the initial call.
             */
            static uint32_t volatile s_cTimes = 0;
            if (!fFirstCall && s_cTimes < 128)
            {
                s_cTimes++;
                SUP_DPRINTF(("supR3HardenedWinReInstallHooks: Reinstalling %s (%p: %.*Rhxs).\n",
                             s_aPatches[i].pszName, pbApi, s_aPatches[i].cbPatch, pbApi));
            }

            Assert(s_aPatches[i].cbPatch >= 4);

            SUPR3HARDENED_ASSERT_NT_SUCCESS(supR3HardenedWinProtectMemory(pbApi, s_aPatches[i].cbPatch, PAGE_EXECUTE_READWRITE));

            /*
             * If we're alone, just memcpy the patch in.
             */

            if (fAmIAlone == ~(ULONG)0)
                fAmIAlone = supR3HardenedWinAmIAlone();
            if (fAmIAlone)
                memcpy(pbApi, s_aPatches[i].pabPatch, s_aPatches[i].cbPatch);
            else
            {
                /*
                 * Not alone.  Start by injecting a JMP $-2, then waste some
                 * CPU cycles to get the other threads a good chance of getting
                 * out of the code before we replace it.
                 */
                RTUINT32U uJmpDollarMinus;
                uJmpDollarMinus.au8[0] = 0xeb;
                uJmpDollarMinus.au8[1] = 0xfe;
                uJmpDollarMinus.au8[2] = pbApi[2];
                uJmpDollarMinus.au8[3] = pbApi[3];
                ASMAtomicXchgU32((uint32_t volatile *)pbApi, uJmpDollarMinus.u);

                NtYieldExecution();
                NtYieldExecution();

                /* Copy in the tail bytes of the patch, then xchg the jmp $-2. */
                if (s_aPatches[i].cbPatch > 4)
                    memcpy(&pbApi[4], &s_aPatches[i].pabPatch[4], s_aPatches[i].cbPatch - 4);
                ASMAtomicXchgU32((uint32_t volatile *)pbApi, *(uint32_t *)s_aPatches[i].pabPatch);
            }

            SUPR3HARDENED_ASSERT_NT_SUCCESS(supR3HardenedWinProtectMemory(pbApi, s_aPatches[i].cbPatch, PAGE_EXECUTE_READ));
        }
    }
}


/**
 * Install hooks for intercepting calls dealing with mapping shared libraries
 * into the process.
 *
 * This allows us to prevent undesirable shared libraries from being loaded.
 *
 * @remarks We assume we're alone in this process, so no seralizing trickery is
 *          necessary when installing the patch.
 *
 * @remarks We would normally just copy the prologue sequence somewhere and add
 *          a jump back at the end of it. But because we wish to avoid
 *          allocating executable memory, we need to have preprepared assembly
 *          "copies".  This makes the non-system call patching a little tedious
 *            and inflexible.
 */
static void supR3HardenedWinInstallHooks(void)
{
    NTSTATUS rcNt;

    /*
     * Disable hard error popups so we can quietly refuse images to be loaded.
     */
    ULONG fHardErr = 0;
    rcNt = NtQueryInformationProcess(NtCurrentProcess(), ProcessDefaultHardErrorMode, &fHardErr, sizeof(fHardErr), NULL);
    if (!NT_SUCCESS(rcNt))
        supR3HardenedFatalMsg("supR3HardenedWinInstallHooks", kSupInitOp_Misc, VERR_GENERAL_FAILURE,
                              "NtQueryInformationProcess/ProcessDefaultHardErrorMode failed: %#x\n", rcNt);
    if (fHardErr & PROCESS_HARDERR_CRITICAL_ERROR)
    {
        fHardErr &= ~PROCESS_HARDERR_CRITICAL_ERROR;
        rcNt = NtSetInformationProcess(NtCurrentProcess(), ProcessDefaultHardErrorMode, &fHardErr, sizeof(fHardErr));
        if (!NT_SUCCESS(rcNt))
            supR3HardenedFatalMsg("supR3HardenedWinInstallHooks", kSupInitOp_Misc, VERR_GENERAL_FAILURE,
                                  "NtSetInformationProcess/ProcessDefaultHardErrorMode failed: %#x\n", rcNt);
    }

    /*
     * Locate the routines first so we can allocate memory that's near enough.
     */
    PFNRT pfnNtCreateSection = supR3HardenedWinGetRealDllSymbol("ntdll.dll", "NtCreateSection");
    SUPR3HARDENED_ASSERT(pfnNtCreateSection != NULL);
    //SUPR3HARDENED_ASSERT(pfnNtCreateSection == (FARPROC)NtCreateSection);

    PFNRT pfnLdrLoadDll = supR3HardenedWinGetRealDllSymbol("ntdll.dll", "LdrLoadDll");
    SUPR3HARDENED_ASSERT(pfnLdrLoadDll != NULL);
    //SUPR3HARDENED_ASSERT(pfnLdrLoadDll == (FARPROC)LdrLoadDll);

    PFNRT pfnKiUserApcDispatcher = supR3HardenedWinGetRealDllSymbol("ntdll.dll", "KiUserApcDispatcher");
    SUPR3HARDENED_ASSERT(pfnKiUserApcDispatcher != NULL);
    g_pfnLdrInitializeThunk = (uintptr_t)supR3HardenedWinGetRealDllSymbol("ntdll.dll", "LdrInitializeThunk");
    SUPR3HARDENED_ASSERT(g_pfnLdrInitializeThunk != NULL);

#ifndef VBOX_WITHOUT_HARDENDED_XCPT_LOGGING
    PFNRT pfnKiUserExceptionDispatcher = supR3HardenedWinGetRealDllSymbol("ntdll.dll", "KiUserExceptionDispatcher");
    SUPR3HARDENED_ASSERT(pfnKiUserExceptionDispatcher != NULL);
#endif

    /*
     * Exec page setup & management.
     */
    uint32_t offExecPage = 0;
    memset(g_abSupHardReadWriteExecPage, 0xcc, PAGE_SIZE);

    /*
     * Hook #1 - NtCreateSection.
     * Purpose: Validate everything that can be mapped into the process before
     *          it's mapped and we still have a file handle to work with.
     */
    uint8_t * const pbNtCreateSection = (uint8_t *)(uintptr_t)pfnNtCreateSection;
    g_pbNtCreateSection = pbNtCreateSection;
    memcpy(g_abNtCreateSectionPatch, pbNtCreateSection, sizeof(g_abNtCreateSectionPatch));

    g_pfnNtCreateSectionReal = NtCreateSection; /* our direct syscall */

#ifdef RT_ARCH_AMD64
    /*
     * Patch 64-bit hosts.
     */
    /* Pattern #1: XP64/W2K3-64 thru Windows 8.1
       0:000> u ntdll!NtCreateSection
       ntdll!NtCreateSection:
       00000000`779f1750 4c8bd1          mov     r10,rcx
       00000000`779f1753 b847000000      mov     eax,47h
       00000000`779f1758 0f05            syscall
       00000000`779f175a c3              ret
       00000000`779f175b 0f1f440000      nop     dword ptr [rax+rax]
       The variant is the value loaded into eax: W2K3=??, Vista=47h?, W7=47h, W80=48h, W81=49h */

    /* Assemble the patch. */
    g_abNtCreateSectionPatch[0]  = 0x48; /* mov rax, qword */
    g_abNtCreateSectionPatch[1]  = 0xb8;
    *(uint64_t *)&g_abNtCreateSectionPatch[2] = (uint64_t)supR3HardenedMonitor_NtCreateSection;
    g_abNtCreateSectionPatch[10] = 0xff; /* jmp rax */
    g_abNtCreateSectionPatch[11] = 0xe0;

#else
    /*
     * Patch 32-bit hosts.
     */
    /* Pattern #1: XP thru Windows 7
            kd> u ntdll!NtCreateSection
            ntdll!NtCreateSection:
            7c90d160 b832000000      mov     eax,32h
            7c90d165 ba0003fe7f      mov     edx,offset SharedUserData!SystemCallStub (7ffe0300)
            7c90d16a ff12            call    dword ptr [edx]
            7c90d16c c21c00          ret     1Ch
            7c90d16f 90              nop
       The variable bit is the value loaded into eax: XP=32h, W2K3=34h, Vista=4bh, W7=54h

       Pattern #2: Windows 8.1
            0:000:x86> u ntdll_6a0f0000!NtCreateSection
            ntdll_6a0f0000!NtCreateSection:
            6a15eabc b854010000      mov     eax,154h
            6a15eac1 e803000000      call    ntdll_6a0f0000!NtCreateSection+0xd (6a15eac9)
            6a15eac6 c21c00          ret     1Ch
            6a15eac9 8bd4            mov     edx,esp
            6a15eacb 0f34            sysenter
            6a15eacd c3              ret
       The variable bit is the value loaded into eax: W81=154h */

    /* Assemble the patch. */
    g_abNtCreateSectionPatch[0] = 0xe9;  /* jmp rel32 */
    *(uint32_t *)&g_abNtCreateSectionPatch[1] = (uintptr_t)supR3HardenedMonitor_NtCreateSection
                                              - (uintptr_t)&pbNtCreateSection[1+4];

#endif

    /*
     * Hook #2 - LdrLoadDll
     * Purpose: (a) Enforce LdrLoadDll search path constraints, and (b) pre-validate
     *          DLLs so we can avoid calling WinVerifyTrust from the first hook,
     *          and thus avoiding messing up the loader data on some installations.
     *
     * This differs from the above function in that is no a system call and
     * we're at the mercy of the compiler.
     */
    uint8_t * const pbLdrLoadDll = (uint8_t *)(uintptr_t)pfnLdrLoadDll;
    g_pbLdrLoadDll = pbLdrLoadDll;
    memcpy(g_abLdrLoadDllPatch, pbLdrLoadDll, sizeof(g_abLdrLoadDllPatch));

    DISSTATE Dis;
    uint32_t cbInstr;
    uint32_t offJmpBack = 0;

#ifdef RT_ARCH_AMD64
    /*
     * Patch 64-bit hosts.
     */
    /* Just use the disassembler to skip 12 bytes or more. */
    while (offJmpBack < 12)
    {
        cbInstr = 1;
        int rc = DISInstr(pbLdrLoadDll + offJmpBack, DISCPUMODE_64BIT, &Dis, &cbInstr);
        if (   RT_FAILURE(rc)
            || (Dis.pCurInstr->fOpType & (DISOPTYPE_CONTROLFLOW))
            || (Dis.ModRM.Bits.Mod == 0 && Dis.ModRM.Bits.Rm == 5 /* wrt RIP */) )
            supR3HardenedWinHookFailed("LdrLoadDll", pbLdrLoadDll);
        offJmpBack += cbInstr;
    }

    /* Assemble the code for resuming the call.*/
    *(PFNRT *)&g_pfnLdrLoadDllReal = (PFNRT)(uintptr_t)&g_abSupHardReadWriteExecPage[offExecPage];

    memcpy(&g_abSupHardReadWriteExecPage[offExecPage], pbLdrLoadDll, offJmpBack);
    offExecPage += offJmpBack;

    g_abSupHardReadWriteExecPage[offExecPage++] = 0xff; /* jmp qword [$+8 wrt RIP] */
    g_abSupHardReadWriteExecPage[offExecPage++] = 0x25;
    *(uint32_t *)&g_abSupHardReadWriteExecPage[offExecPage] = RT_ALIGN_32(offExecPage + 4, 8) - (offExecPage + 4);
    offExecPage = RT_ALIGN_32(offExecPage + 4, 8);
    *(uint64_t *)&g_abSupHardReadWriteExecPage[offExecPage] = (uintptr_t)&pbLdrLoadDll[offJmpBack];
    offExecPage = RT_ALIGN_32(offExecPage + 8, 16);

    /* Assemble the LdrLoadDll patch. */
    Assert(offJmpBack >= 12);
    g_abLdrLoadDllPatch[0]  = 0x48; /* mov rax, qword */
    g_abLdrLoadDllPatch[1]  = 0xb8;
    *(uint64_t *)&g_abLdrLoadDllPatch[2] = (uint64_t)supR3HardenedMonitor_LdrLoadDll;
    g_abLdrLoadDllPatch[10] = 0xff; /* jmp rax */
    g_abLdrLoadDllPatch[11] = 0xe0;

#else
    /*
     * Patch 32-bit hosts.
     */
    /* Just use the disassembler to skip 5 bytes or more. */
    while (offJmpBack < 5)
    {
        cbInstr = 1;
        int rc = DISInstr(pbLdrLoadDll + offJmpBack, DISCPUMODE_32BIT, &Dis, &cbInstr);
        if (   RT_FAILURE(rc)
            || (Dis.pCurInstr->fOpType & (DISOPTYPE_CONTROLFLOW)) )
            supR3HardenedWinHookFailed("LdrLoadDll", pbLdrLoadDll);
        offJmpBack += cbInstr;
    }

    /* Assemble the code for resuming the call.*/
    *(PFNRT *)&g_pfnLdrLoadDllReal = (PFNRT)(uintptr_t)&g_abSupHardReadWriteExecPage[offExecPage];

    memcpy(&g_abSupHardReadWriteExecPage[offExecPage], pbLdrLoadDll, offJmpBack);
    offExecPage += offJmpBack;

    g_abSupHardReadWriteExecPage[offExecPage++] = 0xe9; /* jmp rel32 */
    *(uint32_t *)&g_abSupHardReadWriteExecPage[offExecPage] = (uintptr_t)&pbLdrLoadDll[offJmpBack]
                                                            - (uintptr_t)&g_abSupHardReadWriteExecPage[offExecPage + 4];
    offExecPage = RT_ALIGN_32(offExecPage + 4, 16);

    /* Assemble the LdrLoadDll patch. */
    memcpy(g_abLdrLoadDllPatch, pbLdrLoadDll, sizeof(g_abLdrLoadDllPatch));
    Assert(offJmpBack >= 5);
    g_abLdrLoadDllPatch[0] = 0xe9;
    *(uint32_t *)&g_abLdrLoadDllPatch[1] = (uintptr_t)supR3HardenedMonitor_LdrLoadDll - (uintptr_t)&pbLdrLoadDll[1+4];
#endif

    /*
     * Hook #3 - KiUserApcDispatcher
     * Purpose: Prevent user APC to memory we (or our parent) has freed from
     *          crashing the process.  Also ensures no code injection via user
     *          APC during process init given the way we're vetting the APCs.
     *
     * This differs from the first function in that is no a system call and
     * we're at the mercy of the handwritten assembly.
     *
     * Note! We depend on all waits up past the patching to be non-altertable,
     *       otherwise an APC might slip by us.
     */
    uint8_t * const pbKiUserApcDispatcher = (uint8_t *)(uintptr_t)pfnKiUserApcDispatcher;
    g_pbKiUserApcDispatcher = pbKiUserApcDispatcher;
    memcpy(g_abKiUserApcDispatcherPatch, pbKiUserApcDispatcher, sizeof(g_abKiUserApcDispatcherPatch));

#ifdef RT_ARCH_AMD64
    /*
     * Patch 64-bit hosts.
     */
    /* Just use the disassembler to skip 12 bytes or more. */
    offJmpBack = 0;
    while (offJmpBack < 12)
    {
        cbInstr = 1;
        int rc = DISInstr(pbKiUserApcDispatcher + offJmpBack, DISCPUMODE_64BIT, &Dis, &cbInstr);
        if (   RT_FAILURE(rc)
            || (Dis.pCurInstr->fOpType & (DISOPTYPE_CONTROLFLOW))
            || (Dis.ModRM.Bits.Mod == 0 && Dis.ModRM.Bits.Rm == 5 /* wrt RIP */) )
            supR3HardenedWinHookFailed("KiUserApcDispatcher", pbKiUserApcDispatcher);
        offJmpBack += cbInstr;
    }

    /* Assemble the code for resuming the call.*/
    *(PFNRT *)&g_pfnKiUserApcDispatcherReal = (PFNRT)(uintptr_t)&g_abSupHardReadWriteExecPage[offExecPage];

    memcpy(&g_abSupHardReadWriteExecPage[offExecPage], pbKiUserApcDispatcher, offJmpBack);
    offExecPage += offJmpBack;

    g_abSupHardReadWriteExecPage[offExecPage++] = 0xff; /* jmp qword [$+8 wrt RIP] */
    g_abSupHardReadWriteExecPage[offExecPage++] = 0x25;
    *(uint32_t *)&g_abSupHardReadWriteExecPage[offExecPage] = RT_ALIGN_32(offExecPage + 4, 8) - (offExecPage + 4);
    offExecPage = RT_ALIGN_32(offExecPage + 4, 8);
    *(uint64_t *)&g_abSupHardReadWriteExecPage[offExecPage] = (uintptr_t)&pbKiUserApcDispatcher[offJmpBack];
    offExecPage = RT_ALIGN_32(offExecPage + 8, 16);

    /* Assemble the KiUserApcDispatcher patch. */
    Assert(offJmpBack >= 12);
    g_abKiUserApcDispatcherPatch[0]  = 0x48; /* mov rax, qword */
    g_abKiUserApcDispatcherPatch[1]  = 0xb8;
    *(uint64_t *)&g_abKiUserApcDispatcherPatch[2] = (uint64_t)supR3HardenedMonitor_KiUserApcDispatcher;
    g_abKiUserApcDispatcherPatch[10] = 0xff; /* jmp rax */
    g_abKiUserApcDispatcherPatch[11] = 0xe0;

#else
    /*
     * Patch 32-bit hosts.
     */
    /* Just use the disassembler to skip 5 bytes or more. */
    offJmpBack = 0;
    while (offJmpBack < 5)
    {
        cbInstr = 1;
        int rc = DISInstr(pbKiUserApcDispatcher + offJmpBack, DISCPUMODE_32BIT, &Dis, &cbInstr);
        if (   RT_FAILURE(rc)
            || (Dis.pCurInstr->fOpType & (DISOPTYPE_CONTROLFLOW)) )
            supR3HardenedWinHookFailed("KiUserApcDispatcher", pbKiUserApcDispatcher);
        offJmpBack += cbInstr;
    }

    /* Assemble the code for resuming the call.*/
    *(PFNRT *)&g_pfnKiUserApcDispatcherReal = (PFNRT)(uintptr_t)&g_abSupHardReadWriteExecPage[offExecPage];

    memcpy(&g_abSupHardReadWriteExecPage[offExecPage], pbKiUserApcDispatcher, offJmpBack);
    offExecPage += offJmpBack;

    g_abSupHardReadWriteExecPage[offExecPage++] = 0xe9; /* jmp rel32 */
    *(uint32_t *)&g_abSupHardReadWriteExecPage[offExecPage] = (uintptr_t)&pbKiUserApcDispatcher[offJmpBack]
                                                            - (uintptr_t)&g_abSupHardReadWriteExecPage[offExecPage + 4];
    offExecPage = RT_ALIGN_32(offExecPage + 4, 16);

    /* Assemble the KiUserApcDispatcher patch. */
    memcpy(g_abKiUserApcDispatcherPatch, pbKiUserApcDispatcher, sizeof(g_abKiUserApcDispatcherPatch));
    Assert(offJmpBack >= 5);
    g_abKiUserApcDispatcherPatch[0] = 0xe9;
    *(uint32_t *)&g_abKiUserApcDispatcherPatch[1] = (uintptr_t)supR3HardenedMonitor_KiUserApcDispatcher - (uintptr_t)&pbKiUserApcDispatcher[1+4];
#endif

#ifndef VBOX_WITHOUT_HARDENDED_XCPT_LOGGING
    /*
     * Hook #4 - KiUserExceptionDispatcher
     * Purpose: Logging crashes.
     *
     * This differs from the first function in that is no a system call and
     * we're at the mercy of the handwritten assembly.  This is not mandatory,
     * so we ignore failures here.
     */
    uint8_t * const pbKiUserExceptionDispatcher = (uint8_t *)(uintptr_t)pfnKiUserExceptionDispatcher;
    g_pbKiUserExceptionDispatcher = pbKiUserExceptionDispatcher;
    memcpy(g_abKiUserExceptionDispatcherPatch, pbKiUserExceptionDispatcher, sizeof(g_abKiUserExceptionDispatcherPatch));

# ifdef RT_ARCH_AMD64
    /*
     * Patch 64-bit hosts.
     *
     * Assume the following sequence and replacing the loaded Wow64PrepareForException
     * function pointer with our callback:
     *      cld
     *      mov  rax, Wow64PrepareForException ; Wow64PrepareForException(PCONTEXT, PEXCEPTION_RECORD)
     *      test rax, rax
     *      jz   skip_wow64_callout
     *      <do_callout_thru_rax>
     * (We're not a WOW64 process, so the callout should normally never happen.)
     */
    if (   pbKiUserExceptionDispatcher[ 0] == 0xfc /* CLD */
        && pbKiUserExceptionDispatcher[ 1] == 0x48 /* MOV RAX, symbol wrt rip */
        && pbKiUserExceptionDispatcher[ 2] == 0x8b
        && pbKiUserExceptionDispatcher[ 3] == 0x05
        && pbKiUserExceptionDispatcher[ 8] == 0x48 /* TEST RAX, RAX */
        && pbKiUserExceptionDispatcher[ 9] == 0x85
        && pbKiUserExceptionDispatcher[10] == 0xc0
        && pbKiUserExceptionDispatcher[11] == 0x74)
    {
        /* Assemble the KiUserExceptionDispatcher patch. */
        g_abKiUserExceptionDispatcherPatch[1]  = 0x48; /* MOV RAX, supR3HardenedMonitor_KiUserExceptionDispatcher */
        g_abKiUserExceptionDispatcherPatch[2]  = 0xb8;
        *(uint64_t *)&g_abKiUserExceptionDispatcherPatch[3] = (uint64_t)supR3HardenedMonitor_KiUserExceptionDispatcher;
        g_abKiUserExceptionDispatcherPatch[11] = 0x90; /* NOP (was JZ) */
        g_abKiUserExceptionDispatcherPatch[12] = 0x90; /* NOP (was DISP8 of JZ) */
    }
    else
        SUP_DPRINTF(("supR3HardenedWinInstallHooks: failed to patch KiUserExceptionDispatcher (%.20Rhxs)\n",
                     pbKiUserExceptionDispatcher));
# else
    /*
     * Patch 32-bit hosts.
     */
    /* Just use the disassembler to skip 5 bytes or more. */
    offJmpBack = 0;
    while (offJmpBack < 5)
    {
        cbInstr = 1;
        int rc = DISInstr(pbKiUserExceptionDispatcher + offJmpBack, DISCPUMODE_32BIT, &Dis, &cbInstr);
        if (   RT_FAILURE(rc)
            || (Dis.pCurInstr->fOpType & (DISOPTYPE_CONTROLFLOW)) )
        {
            SUP_DPRINTF(("supR3HardenedWinInstallHooks: failed to patch KiUserExceptionDispatcher (off %#x in %.20Rhxs)\n",
                         offJmpBack, pbKiUserExceptionDispatcher));
            break;
        }
        offJmpBack += cbInstr;
    }
    if (offJmpBack >= 5)
    {
        /* Assemble the code for resuming the call.*/
        *(PFNRT *)&g_pfnKiUserExceptionDispatcherReal = (PFNRT)(uintptr_t)&g_abSupHardReadWriteExecPage[offExecPage];

        memcpy(&g_abSupHardReadWriteExecPage[offExecPage], pbKiUserExceptionDispatcher, offJmpBack);
        offExecPage += offJmpBack;

        g_abSupHardReadWriteExecPage[offExecPage++] = 0xe9; /* jmp rel32 */
        *(uint32_t *)&g_abSupHardReadWriteExecPage[offExecPage] = (uintptr_t)&pbKiUserExceptionDispatcher[offJmpBack]
                                                                - (uintptr_t)&g_abSupHardReadWriteExecPage[offExecPage + 4];
        offExecPage = RT_ALIGN_32(offExecPage + 4, 16);

        /* Assemble the KiUserExceptionDispatcher patch. */
        memcpy(g_abKiUserExceptionDispatcherPatch, pbKiUserExceptionDispatcher, sizeof(g_abKiUserExceptionDispatcherPatch));
        Assert(offJmpBack >= 5);
        g_abKiUserExceptionDispatcherPatch[0] = 0xe9;
        *(uint32_t *)&g_abKiUserExceptionDispatcherPatch[1] = (uintptr_t)supR3HardenedMonitor_KiUserExceptionDispatcher - (uintptr_t)&pbKiUserExceptionDispatcher[1+4];
    }
# endif
#endif /* !VBOX_WITHOUT_HARDENDED_XCPT_LOGGING */

    /*
     * Seal the rwx page.
     */
    SUPR3HARDENED_ASSERT_NT_SUCCESS(supR3HardenedWinProtectMemory(g_abSupHardReadWriteExecPage, PAGE_SIZE, PAGE_EXECUTE_READ));

    /*
     * Install the patches.
     */
    supR3HardenedWinReInstallHooks(true /*fFirstCall*/);
}






/*
 *
 * T h r e a d   c r e a t i o n   c o n t r o l
 * T h r e a d   c r e a t i o n   c o n t r o l
 * T h r e a d   c r e a t i o n   c o n t r o l
 *
 */


/**
 * Common code used for child and parent to make new threads exit immediately.
 *
 * This patches the LdrInitializeThunk code to call NtTerminateThread with
 * STATUS_SUCCESS instead of doing the NTDLL initialization.
 *
 * @returns VBox status code.
 * @param   hProcess            The process to do this to.
 * @param   pvLdrInitThunk      The address of the LdrInitializeThunk code to
 *                              override.
 * @param   pvNtTerminateThread The address of the NtTerminateThread function in
 *                              the NTDLL instance we're patching.  (Must be +/-
 *                              2GB from the thunk code.)
 * @param   pabBackup           Where to back up the original instruction bytes
 *                              at pvLdrInitThunk.
 * @param   cbBackup            The size of the backup area. Must be 16 bytes.
 * @param   pErrInfo            Where to return extended error information.
 *                              Optional.
 */
static int supR3HardNtDisableThreadCreationEx(HANDLE hProcess, void *pvLdrInitThunk, void *pvNtTerminateThread,
                                              uint8_t *pabBackup, size_t cbBackup, PRTERRINFO pErrInfo)
{
    SUP_DPRINTF(("supR3HardNtDisableThreadCreation: pvLdrInitThunk=%p pvNtTerminateThread=%p\n", pvLdrInitThunk, pvNtTerminateThread));
    SUPR3HARDENED_ASSERT(cbBackup == 16);
    SUPR3HARDENED_ASSERT(RT_ABS((intptr_t)pvLdrInitThunk - (intptr_t)pvNtTerminateThread) < 16*_1M);

    /*
     * Back up the thunk code.
     */
    SIZE_T  cbIgnored;
    NTSTATUS rcNt = NtReadVirtualMemory(hProcess, pvLdrInitThunk, pabBackup, cbBackup, &cbIgnored);
    if (!NT_SUCCESS(rcNt))
        return RTErrInfoSetF(pErrInfo, VERR_GENERAL_FAILURE,
                             "supR3HardNtDisableThreadCreation: NtReadVirtualMemory/LdrInitializeThunk failed: %#x", rcNt);

    /*
     * Cook up replacement code that calls NtTerminateThread.
     */
    uint8_t abReplacement[16];
    memcpy(abReplacement, pabBackup, sizeof(abReplacement));

#ifdef RT_ARCH_AMD64
    abReplacement[0] = 0x31;    /* xor ecx, ecx */
    abReplacement[1] = 0xc9;
    abReplacement[2] = 0x31;    /* xor edx, edx */
    abReplacement[3] = 0xd2;
    abReplacement[4] = 0xe8;    /* call near NtTerminateThread */
    *(int32_t *)&abReplacement[5] = (int32_t)((uintptr_t)pvNtTerminateThread - ((uintptr_t)pvLdrInitThunk + 9));
    abReplacement[9] = 0xcc;    /* int3 */
#elif defined(RT_ARCH_X86)
    abReplacement[0] = 0x6a;    /* push 0 */
    abReplacement[1] = 0x00;
    abReplacement[2] = 0x6a;    /* push 0 */
    abReplacement[3] = 0x00;
    abReplacement[4] = 0xe8;    /* call near NtTerminateThread */
    *(int32_t *)&abReplacement[5] = (int32_t)((uintptr_t)pvNtTerminateThread - ((uintptr_t)pvLdrInitThunk + 9));
    abReplacement[9] = 0xcc;    /* int3 */
#else
# error "Unsupported arch."
#endif

    /*
     * Install the replacment code.
     */
    PVOID  pvProt   = pvLdrInitThunk;
    SIZE_T cbProt   = cbBackup;
    ULONG  fOldProt = 0;
    rcNt = NtProtectVirtualMemory(hProcess, &pvProt, &cbProt, PAGE_EXECUTE_READWRITE, &fOldProt);
    if (!NT_SUCCESS(rcNt))
        return RTErrInfoSetF(pErrInfo, VERR_GENERAL_FAILURE,
                             "supR3HardNtDisableThreadCreationEx: NtProtectVirtualMemory/LdrInitializeThunk failed: %#x", rcNt);

    rcNt = NtWriteVirtualMemory(hProcess, pvLdrInitThunk, abReplacement, sizeof(abReplacement), &cbIgnored);
    if (!NT_SUCCESS(rcNt))
        return RTErrInfoSetF(pErrInfo, VERR_GENERAL_FAILURE,
                             "supR3HardNtDisableThreadCreationEx: NtWriteVirtualMemory/LdrInitializeThunk failed: %#x", rcNt);

    pvProt   = pvLdrInitThunk;
    cbProt   = cbBackup;
    rcNt = NtProtectVirtualMemory(hProcess, &pvProt, &cbProt, fOldProt, &fOldProt);
    if (!NT_SUCCESS(rcNt))
        return RTErrInfoSetF(pErrInfo, VERR_GENERAL_FAILURE,
                             "supR3HardNtDisableThreadCreationEx: NtProtectVirtualMemory/LdrInitializeThunk/2 failed: %#x", rcNt);

    return VINF_SUCCESS;
}


/**
 * Undo the effects of supR3HardNtDisableThreadCreationEx.
 *
 * @returns VBox status code.
 * @param   hProcess            The process to do this to.
 * @param   pvLdrInitThunk      The address of the LdrInitializeThunk code to
 *                              override.
 * @param   pabBackup           Where to back up the original instruction bytes
 *                              at pvLdrInitThunk.
 * @param   cbBackup            The size of the backup area. Must be 16 bytes.
 * @param   pErrInfo            Where to return extended error information.
 *                              Optional.
 */
static int supR3HardNtEnableThreadCreationEx(HANDLE hProcess, void *pvLdrInitThunk, uint8_t const *pabBackup, size_t cbBackup,
                                             PRTERRINFO pErrInfo)
{
    SUP_DPRINTF(("supR3HardNtEnableThreadCreationEx:\n"));
    SUPR3HARDENED_ASSERT(cbBackup == 16);

    PVOID  pvProt   = pvLdrInitThunk;
    SIZE_T cbProt   = cbBackup;
    ULONG  fOldProt = 0;
    NTSTATUS rcNt = NtProtectVirtualMemory(hProcess, &pvProt, &cbProt, PAGE_EXECUTE_READWRITE, &fOldProt);
    if (!NT_SUCCESS(rcNt))
        return RTErrInfoSetF(pErrInfo, VERR_GENERAL_FAILURE,
                             "supR3HardNtEnableThreadCreationEx: NtProtectVirtualMemory/LdrInitializeThunk failed: %#x", rcNt);

    SIZE_T cbIgnored;
    rcNt = NtWriteVirtualMemory(hProcess, pvLdrInitThunk, pabBackup, cbBackup, &cbIgnored);
    if (!NT_SUCCESS(rcNt))
        return RTErrInfoSetF(pErrInfo, VERR_GENERAL_FAILURE,
                             "supR3HardNtEnableThreadCreationEx: NtWriteVirtualMemory/LdrInitializeThunk[restore] failed: %#x",
                             rcNt);

    pvProt   = pvLdrInitThunk;
    cbProt   = cbBackup;
    rcNt = NtProtectVirtualMemory(hProcess, &pvProt, &cbProt, fOldProt, &fOldProt);
    if (!NT_SUCCESS(rcNt))
        return RTErrInfoSetF(pErrInfo, VERR_GENERAL_FAILURE,
                             "supR3HardNtEnableThreadCreationEx: NtProtectVirtualMemory/LdrInitializeThunk[restore] failed: %#x",
                             rcNt);

    return VINF_SUCCESS;
}


/**
 * Disable thread creation for the current process.
 *
 * @remarks Doesn't really disables it, just makes the threads exit immediately
 *          without executing any real code.
 */
static void supR3HardenedWinDisableThreadCreation(void)
{
    /* Cannot use the imported NtTerminateThread as it's pointing to our own
       syscall assembly code. */
    static PFNRT s_pfnNtTerminateThread = NULL;
    if (s_pfnNtTerminateThread == NULL)
        s_pfnNtTerminateThread = supR3HardenedWinGetRealDllSymbol("ntdll.dll", "NtTerminateThread");
    SUPR3HARDENED_ASSERT(s_pfnNtTerminateThread);

    int rc = supR3HardNtDisableThreadCreationEx(NtCurrentProcess(),
                                                (void *)(uintptr_t)&LdrInitializeThunk,
                                                (void *)(uintptr_t)s_pfnNtTerminateThread,
                                                g_abLdrInitThunkSelfBackup, sizeof(g_abLdrInitThunkSelfBackup),
                                                NULL /* pErrInfo*/);
    g_fSupInitThunkSelfPatched = RT_SUCCESS(rc);
}


/**
 * Undoes the effects of supR3HardenedWinDisableThreadCreation.
 */
DECLHIDDEN(void) supR3HardenedWinEnableThreadCreation(void)
{
    if (g_fSupInitThunkSelfPatched)
    {
        int rc = supR3HardNtEnableThreadCreationEx(NtCurrentProcess(),
                                                   (void *)(uintptr_t)&LdrInitializeThunk,
                                                   g_abLdrInitThunkSelfBackup, sizeof(g_abLdrInitThunkSelfBackup),
                                                   RTErrInfoInitStatic(&g_ErrInfoStatic));
        if (RT_FAILURE(rc))
            supR3HardenedError(rc, true /*fFatal*/, "%s", g_ErrInfoStatic.szMsg);
        g_fSupInitThunkSelfPatched = false;
    }
}




/*
 *
 * R e s p a w n
 * R e s p a w n
 * R e s p a w n
 *
 */


/**
 * Gets the SID of the user associated with the process.
 *
 * @returns @c true if we've got a login SID, @c false if not.
 * @param   pSidUser            Where to return the user SID.
 * @param   cbSidUser           The size of the user SID buffer.
 * @param   pSidLogin           Where to return the login SID.
 * @param   cbSidLogin          The size of the login SID buffer.
 */
static bool supR3HardNtChildGetUserAndLogSids(PSID pSidUser, ULONG cbSidUser, PSID pSidLogin, ULONG cbSidLogin)
{
    HANDLE hToken;
    SUPR3HARDENED_ASSERT_NT_SUCCESS(NtOpenProcessToken(NtCurrentProcess(), TOKEN_QUERY, &hToken));
    union
    {
        TOKEN_USER      UserInfo;
        TOKEN_GROUPS    Groups;
        uint8_t         abPadding[4096];
    } uBuf;
    ULONG cbRet = 0;
    SUPR3HARDENED_ASSERT_NT_SUCCESS(NtQueryInformationToken(hToken, TokenUser, &uBuf, sizeof(uBuf), &cbRet));
    SUPR3HARDENED_ASSERT_NT_SUCCESS(RtlCopySid(cbSidUser, pSidUser, uBuf.UserInfo.User.Sid));

    bool fLoginSid = false;
    NTSTATUS rcNt = NtQueryInformationToken(hToken, TokenLogonSid, &uBuf, sizeof(uBuf), &cbRet);
    if (NT_SUCCESS(rcNt))
    {
        for (DWORD i = 0; i < uBuf.Groups.GroupCount; i++)
            if ((uBuf.Groups.Groups[i].Attributes & SE_GROUP_LOGON_ID) == SE_GROUP_LOGON_ID)
            {
                SUPR3HARDENED_ASSERT_NT_SUCCESS(RtlCopySid(cbSidLogin, pSidLogin, uBuf.Groups.Groups[i].Sid));
                fLoginSid = true;
                break;
            }
    }

    SUPR3HARDENED_ASSERT_NT_SUCCESS(NtClose(hToken));

    return fLoginSid;
}


/**
 * Build security attributes for the process or the primary thread (@a fProcess)
 *
 * Process DACLs can be bypassed using the SeDebugPrivilege (generally available
 * to admins, i.e. normal windows users), or by taking ownership and/or
 * modifying the DACL.  However, it restricts
 *
 * @param   pSecAttrs           Where to return the security attributes.
 * @param   pCleanup            Cleanup record.
 * @param   fProcess            Set if it's for the process, clear if it's for
 *                              the primary thread.
 */
static void supR3HardNtChildInitSecAttrs(PSECURITY_ATTRIBUTES pSecAttrs, PMYSECURITYCLEANUP pCleanup, bool fProcess)
{
    /*
     * Safe return values.
     */
    suplibHardenedMemSet(pCleanup, 0, sizeof(*pCleanup));

    pSecAttrs->nLength              = sizeof(*pSecAttrs);
    pSecAttrs->bInheritHandle       = FALSE;
    pSecAttrs->lpSecurityDescriptor = NULL;

/** @todo This isn't at all complete, just sketches... */

    /*
     * Create an ACL detailing the access of the above groups.
     */
    SUPR3HARDENED_ASSERT_NT_SUCCESS(RtlCreateAcl(&pCleanup->Acl.AclHdr, sizeof(pCleanup->Acl), ACL_REVISION));

    ULONG fDeny  = DELETE | WRITE_DAC | WRITE_OWNER;
    ULONG fAllow = SYNCHRONIZE | READ_CONTROL;
    ULONG fAllowLogin = SYNCHRONIZE | READ_CONTROL;
    if (fProcess)
    {
        fDeny       |= PROCESS_CREATE_THREAD | PROCESS_SET_SESSIONID | PROCESS_VM_OPERATION | PROCESS_VM_WRITE
                    |  PROCESS_CREATE_PROCESS | PROCESS_DUP_HANDLE | PROCESS_SET_QUOTA
                    |  PROCESS_SET_INFORMATION | PROCESS_SUSPEND_RESUME;
        fAllow      |= PROCESS_TERMINATE | PROCESS_VM_READ | PROCESS_QUERY_INFORMATION;
        fAllowLogin |= PROCESS_TERMINATE | PROCESS_VM_READ | PROCESS_QUERY_INFORMATION;
        if (g_uNtVerCombined >= SUP_MAKE_NT_VER_SIMPLE(6, 0)) /* Introduced in Vista. */
        {
            fAllow      |= PROCESS_QUERY_LIMITED_INFORMATION;
            fAllowLogin |= PROCESS_QUERY_LIMITED_INFORMATION;
        }
        if (g_uNtVerCombined >= SUP_MAKE_NT_VER_SIMPLE(6, 3)) /* Introduced in Windows 8.1. */
            fAllow  |= PROCESS_SET_LIMITED_INFORMATION;
    }
    else
    {
        fDeny       |= THREAD_SUSPEND_RESUME | THREAD_SET_CONTEXT | THREAD_SET_INFORMATION | THREAD_SET_THREAD_TOKEN
                    |  THREAD_IMPERSONATE | THREAD_DIRECT_IMPERSONATION;
        fAllow      |= THREAD_GET_CONTEXT | THREAD_QUERY_INFORMATION;
        fAllowLogin |= THREAD_GET_CONTEXT | THREAD_QUERY_INFORMATION;
        if (g_uNtVerCombined >= SUP_MAKE_NT_VER_SIMPLE(6, 0)) /* Introduced in Vista. */
        {
            fAllow      |= THREAD_QUERY_LIMITED_INFORMATION | THREAD_SET_LIMITED_INFORMATION;
            fAllowLogin |= THREAD_QUERY_LIMITED_INFORMATION;
        }

    }
    fDeny |= ~fAllow & (SPECIFIC_RIGHTS_ALL | STANDARD_RIGHTS_ALL);

    /* Deny everyone access to bad bits. */
#if 1
    SID_IDENTIFIER_AUTHORITY SIDAuthWorld = SECURITY_WORLD_SID_AUTHORITY;
    SUPR3HARDENED_ASSERT_NT_SUCCESS(RtlInitializeSid(&pCleanup->Everyone.Sid, &SIDAuthWorld, 1));
    *RtlSubAuthoritySid(&pCleanup->Everyone.Sid, 0) = SECURITY_WORLD_RID;
    SUPR3HARDENED_ASSERT_NT_SUCCESS(RtlAddAccessDeniedAce(&pCleanup->Acl.AclHdr, ACL_REVISION,
                                                          fDeny, &pCleanup->Everyone.Sid));
#endif

#if 0
    /* Grant some access to the owner - doesn't work. */
    SID_IDENTIFIER_AUTHORITY SIDAuthCreator = SECURITY_CREATOR_SID_AUTHORITY;
    SUPR3HARDENED_ASSERT_NT_SUCCESS(RtlInitializeSid(&pCleanup->Owner.Sid, &SIDAuthCreator, 1));
    *RtlSubAuthoritySid(&pCleanup->Owner.Sid, 0) = SECURITY_CREATOR_OWNER_RID;

    SUPR3HARDENED_ASSERT_NT_SUCCESS(RtlAddAccessDeniedAce(&pCleanup->Acl.AclHdr, ACL_REVISION,
                                                          fDeny, &pCleanup->Owner.Sid));
    SUPR3HARDENED_ASSERT_NT_SUCCESS(RtlAddAccessAllowedAce(&pCleanup->Acl.AclHdr, ACL_REVISION,
                                                           fAllow, &pCleanup->Owner.Sid));
#endif

#if 1
    bool fHasLoginSid = supR3HardNtChildGetUserAndLogSids(&pCleanup->User.Sid, sizeof(pCleanup->User),
                                                          &pCleanup->Login.Sid, sizeof(pCleanup->Login));

# if 1
    /* Grant minimal access to the user. */
    SUPR3HARDENED_ASSERT_NT_SUCCESS(RtlAddAccessDeniedAce(&pCleanup->Acl.AclHdr, ACL_REVISION,
                                                          fDeny, &pCleanup->User.Sid));
    SUPR3HARDENED_ASSERT_NT_SUCCESS(RtlAddAccessAllowedAce(&pCleanup->Acl.AclHdr, ACL_REVISION,
                                                           fAllow, &pCleanup->User.Sid));
# endif

# if 1
    /* Grant very limited access to the login sid. */
    if (fHasLoginSid)
    {
        SUPR3HARDENED_ASSERT_NT_SUCCESS(RtlAddAccessAllowedAce(&pCleanup->Acl.AclHdr, ACL_REVISION,
                                                               fAllowLogin, &pCleanup->Login.Sid));
    }
# endif

#endif

    /*
     * Create a security descriptor with the above ACL.
     */
    PSECURITY_DESCRIPTOR pSecDesc = (PSECURITY_DESCRIPTOR)RTMemAllocZ(SECURITY_DESCRIPTOR_MIN_LENGTH);
    pCleanup->pSecDesc = pSecDesc;

    SUPR3HARDENED_ASSERT_NT_SUCCESS(RtlCreateSecurityDescriptor(pSecDesc, SECURITY_DESCRIPTOR_REVISION));
    SUPR3HARDENED_ASSERT_NT_SUCCESS(RtlSetDaclSecurityDescriptor(pSecDesc, TRUE /*fDaclPresent*/, &pCleanup->Acl.AclHdr,
                                                                 FALSE /*fDaclDefaulted*/));
    pSecAttrs->lpSecurityDescriptor = pSecDesc;
}


/**
 * Predicate function which tests whether @a ch is a argument separator
 * character.
 *
 * @returns True/false.
 * @param   ch                  The character to examine.
 */
DECLINLINE(bool) suplibCommandLineIsArgSeparator(int ch)
{
    return ch == ' '
        || ch == '\t'
        || ch == '\n'
        || ch == '\r';
}


/**
 * Construct the new command line.
 *
 * Since argc/argv are both derived from GetCommandLineW (see
 * suplibHardenedWindowsMain), we skip the argument by argument UTF-8 -> UTF-16
 * conversion and quoting by going to the original source.
 *
 * The executable name, though, is replaced in case it's not a fullly
 * qualified path.
 *
 * The re-spawn indicator is added immediately after the executable name
 * so that we don't get tripped up missing close quote chars in the last
 * argument.
 *
 * @returns Pointer to a command line string (heap).
 * @param   pString         Unicode string structure to initialize to the
 *                          command line. Optional.
 * @param   iWhich          Which respawn we're to check for, 1 being the first
 *                          one, and 2 the second and final.
 */
static PRTUTF16 supR3HardNtChildConstructCmdLine(PUNICODE_STRING pString, int iWhich)
{
    SUPR3HARDENED_ASSERT(iWhich == 1 || iWhich == 2);

    /*
     * Get the command line and skip the executable name.
     */
    PUNICODE_STRING pCmdLineStr = &NtCurrentPeb()->ProcessParameters->CommandLine;
    PCRTUTF16 pawcArgs = pCmdLineStr->Buffer;
    uint32_t  cwcArgs  = pCmdLineStr->Length / sizeof(WCHAR);

    /* Skip leading space (shouldn't be any, but whatever). */
    while (cwcArgs > 0 && suplibCommandLineIsArgSeparator(*pawcArgs) )
        cwcArgs--, pawcArgs++;
    SUPR3HARDENED_ASSERT(cwcArgs > 0 && *pawcArgs != '\0');

    /* Walk to the end of it. */
    int fQuoted = false;
    do
    {
        if (*pawcArgs == '"')
        {
            fQuoted = !fQuoted;
            cwcArgs--; pawcArgs++;
        }
        else if (*pawcArgs != '\\' || (pawcArgs[1] != '\\' && pawcArgs[1] != '"'))
            cwcArgs--, pawcArgs++;
        else
        {
            unsigned cSlashes = 0;
            do
            {
                cSlashes++;
                cwcArgs--;
                pawcArgs++;
            }
            while (cwcArgs > 0 && *pawcArgs == '\\');
            if (cwcArgs > 0 && *pawcArgs == '"' && (cSlashes & 1))
                cwcArgs--, pawcArgs++; /* odd number of slashes == escaped quote */
        }
    } while (cwcArgs > 0 && (fQuoted || !suplibCommandLineIsArgSeparator(*pawcArgs)));

    /* Skip trailing spaces. */
    while (cwcArgs > 0 && suplibCommandLineIsArgSeparator(*pawcArgs))
        cwcArgs--, pawcArgs++;

    /*
     * Allocate a new buffer.
     */
    AssertCompile(sizeof(SUPR3_RESPAWN_1_ARG0) == sizeof(SUPR3_RESPAWN_2_ARG0));
    size_t cwcCmdLine = (sizeof(SUPR3_RESPAWN_1_ARG0) - 1) / sizeof(SUPR3_RESPAWN_1_ARG0[0]) /* Respawn exe name. */
                      + !!cwcArgs + cwcArgs; /* if arguments present, add space + arguments. */
    if (cwcCmdLine * sizeof(WCHAR) >= 0xfff0)
        supR3HardenedFatalMsg("supR3HardNtChildConstructCmdLine", kSupInitOp_Misc, VERR_OUT_OF_RANGE,
                              "Command line is too long (%u chars)!", cwcCmdLine);

    PRTUTF16 pwszCmdLine = (PRTUTF16)RTMemAlloc((cwcCmdLine + 1) * sizeof(RTUTF16));
    SUPR3HARDENED_ASSERT(pwszCmdLine != NULL);

    /*
     * Construct the new command line.
     */
    PRTUTF16 pwszDst = pwszCmdLine;
    for (const char *pszSrc = iWhich == 1 ? SUPR3_RESPAWN_1_ARG0 : SUPR3_RESPAWN_2_ARG0; *pszSrc; pszSrc++)
        *pwszDst++ = *pszSrc;

    if (cwcArgs)
    {
        *pwszDst++ = ' ';
        suplibHardenedMemCopy(pwszDst, pawcArgs, cwcArgs * sizeof(RTUTF16));
        pwszDst += cwcArgs;
    }

    *pwszDst = '\0';
    SUPR3HARDENED_ASSERT((uintptr_t)(pwszDst - pwszCmdLine) == cwcCmdLine);

    if (pString)
    {
        pString->Buffer = pwszCmdLine;
        pString->Length = (USHORT)(cwcCmdLine * sizeof(WCHAR));
        pString->MaximumLength = pString->Length + sizeof(WCHAR);
    }
    return pwszCmdLine;
}


/**
 * Terminates the child process.
 *
 * @param   hProcess            The process handle.
 * @param   pszWhere            Who's having child rasing troubles.
 * @param   rc                  The status code to report.
 * @param   pszFormat           The message format string.
 * @param   ...                 Message format arguments.
 */
static void supR3HardenedWinKillChild(HANDLE hProcess, const char *pszWhere, int rc, const char *pszFormat, ...)
{
    /*
     * Terminate the process ASAP and display error.
     */
    NtTerminateProcess(hProcess, RTEXITCODE_FAILURE);

    va_list va;
    va_start(va, pszFormat);
    supR3HardenedErrorV(rc, false /*fFatal*/, pszFormat, va);
    va_end(va);

    /*
     * Wait for the process to really go away.
     */
    PROCESS_BASIC_INFORMATION BasicInfo;
    NTSTATUS rcNtExit = NtQueryInformationProcess(hProcess, ProcessBasicInformation, &BasicInfo, sizeof(BasicInfo), NULL);
    bool fExitOk = NT_SUCCESS(rcNtExit) && BasicInfo.ExitStatus != STATUS_PENDING;
    if (!fExitOk)
    {
        NTSTATUS rcNtWait;
        uint64_t uMsTsStart = supR3HardenedWinGetMilliTS();
        do
        {
            NtTerminateProcess(hProcess, DBG_TERMINATE_PROCESS);

            LARGE_INTEGER Timeout;
            Timeout.QuadPart = -20000000; /* 2 second */
            rcNtWait = NtWaitForSingleObject(hProcess, TRUE /*Alertable*/, &Timeout);

            rcNtExit = NtQueryInformationProcess(hProcess, ProcessBasicInformation, &BasicInfo, sizeof(BasicInfo), NULL);
            fExitOk = NT_SUCCESS(rcNtExit) && BasicInfo.ExitStatus != STATUS_PENDING;
        } while (   !fExitOk
                 && (   rcNtWait == STATUS_TIMEOUT
                     || rcNtWait == STATUS_USER_APC
                     || rcNtWait == STATUS_ALERTED)
                 && supR3HardenedWinGetMilliTS() - uMsTsStart < 60 * 1000);
        if (fExitOk)
            supR3HardenedError(rc, false /*fFatal*/,
                               "NtDuplicateObject failed and we failed to kill child: rc=%u (%#x) rcNtWait=%#x hProcess=%p\n",
                               rc, rc, rcNtWait, hProcess);
    }

    /*
     * Final error message.
     */
    va_start(va, pszFormat);
    supR3HardenedFatalMsgV(pszWhere, kSupInitOp_Misc, rc, pszFormat, va);
    /* not reached */
}


/**
 * Checks the child process when hEvtParent is signalled.
 *
 * This will read the request data from the child and check it against expected
 * request.  If an error is signalled, we'll raise it and make sure the child
 * terminates before terminating the calling process.
 *
 * @param   pThis               The child process data structure.
 * @param   enmExpectedRequest  The expected child request.
 * @param   pszWhat             What we're waiting for.
 */
static void supR3HardNtChildProcessRequest(PSUPR3HARDNTCHILD pThis, SUPR3WINCHILDREQ enmExpectedRequest, const char *pszWhat)
{
    /*
     * Read the process parameters from the child.
     */
    uintptr_t           uChildAddr = (uintptr_t)pThis->Peb.ImageBaseAddress
                                   + ((uintptr_t)&g_ProcParams - (uintptr_t)NtCurrentPeb()->ImageBaseAddress);
    SIZE_T              cbIgnored  = 0;
    RT_ZERO(pThis->ProcParams);
    NTSTATUS rcNt = NtReadVirtualMemory(pThis->hProcess, (PVOID)uChildAddr,
                                        &pThis->ProcParams, sizeof(pThis->ProcParams), &cbIgnored);
    if (!NT_SUCCESS(rcNt))
        supR3HardenedWinKillChild(pThis, "supR3HardNtChildProcessRequest", rcNt,
                                  "NtReadVirtualMemory(,%p,) failed reading child process status: %#x\n", uChildAddr, rcNt);

    /*
     * Is it the expected request?
     */
    if (pThis->ProcParams.enmRequest == enmExpectedRequest)
        return;

    /*
     * No, not the expected request. If it's an error request, tell the child
     * to terminate itself, otherwise we'll have to terminate it.
     */
    pThis->ProcParams.szErrorMsg[sizeof(pThis->ProcParams.szErrorMsg) - 1] = '\0';
    pThis->ProcParams.szWhere[sizeof(pThis->ProcParams.szWhere) - 1] = '\0';
    SUP_DPRINTF(("supR3HardenedWinCheckChild: enmRequest=%d rc=%d enmWhat=%d %s: %s\n",
                 pThis->ProcParams.enmRequest, pThis->ProcParams.rc, pThis->ProcParams.enmWhat,
                 pThis->ProcParams.szWhere, pThis->ProcParams.szErrorMsg));

    if (pThis->ProcParams.enmRequest != kSupR3WinChildReq_Error)
        supR3HardenedWinKillChild(pThis, "supR3HardenedWinCheckChild", VERR_INVALID_PARAMETER,
                                  "Unexpected child request #%d. Was expecting #%d (%s).\n",
                                  pThis->ProcParams.enmRequest, enmExpectedRequest, pszWhat);

    rcNt = NtSetEvent(pThis->hEvtChild, NULL);
    if (!NT_SUCCESS(rcNt))
        supR3HardenedWinKillChild(pThis, "supR3HardNtChildProcessRequest", rcNt, "NtSetEvent failed: %#x\n", rcNt);

    /* Wait for it to terminate. */
    LARGE_INTEGER Timeout;
    Timeout.QuadPart = -50000000; /* 5 seconds */
    rcNt = NtWaitForSingleObject(pThis->hProcess, FALSE /*Alertable*/, &Timeout);
    if (rcNt != STATUS_WAIT_0)
    {
        SUP_DPRINTF(("supR3HardNtChildProcessRequest: Child is taking too long to quit (rcWait=%#x), killing it...\n", rcNt));
        NtTerminateProcess(pThis->hProcess, DBG_TERMINATE_PROCESS);
    }

    /*
     * Report the error in the same way as it occured in the guest.
     */
    if (pThis->ProcParams.enmWhat == kSupInitOp_Invalid)
        supR3HardenedFatalMsg("supR3HardenedWinCheckChild", kSupInitOp_Misc, pThis->ProcParams.rc,
                              "%s", pThis->ProcParams.szErrorMsg);
    else
        supR3HardenedFatalMsg(pThis->ProcParams.szWhere, pThis->ProcParams.enmWhat, pThis->ProcParams.rc,
                              "%s", pThis->ProcParams.szErrorMsg);
}


/**
 * Waits for the child to make a certain request or terminate.
 *
 * The stub process will also wait on it's parent to terminate.
 * This call will only return if the child made the expected request.
 *
 * @param   pThis               The child process data structure.
 * @param   enmExpectedRequest  The child request to wait for.
 * @param   cMsTimeout          The number of milliseconds to wait (at least).
 * @param   pszWhat             What we're waiting for.
 */
static void supR3HardNtChildWaitFor(PSUPR3HARDNTCHILD pThis, SUPR3WINCHILDREQ enmExpectedRequest, RTMSINTERVAL cMsTimeout,
                                    const char *pszWhat)
{
    /*
     * The wait loop.
     * Will return when the expected request arrives.
     * Will break out when one of the processes terminates.
     */
    NTSTATUS      rcNtWait;
    LARGE_INTEGER Timeout;
    uint64_t      uMsTsStart = supR3HardenedWinGetMilliTS();
    uint64_t      cMsElapsed = 0;
    for (;;)
    {
        /*
         * Assemble handles to wait for.
         */
        ULONG  cHandles = 1;
        HANDLE ahHandles[3];
        ahHandles[0] = pThis->hProcess;
        if (pThis->hEvtParent)
            ahHandles[cHandles++] = pThis->hEvtParent;
        if (pThis->hParent)
            ahHandles[cHandles++] = pThis->hParent;

        /*
         * Do the waiting according to the callers wishes.
         */
        if (   enmExpectedRequest == kSupR3WinChildReq_End
            || cMsTimeout  == RT_INDEFINITE_WAIT)
            rcNtWait = NtWaitForMultipleObjects(cHandles, &ahHandles[0], WaitAnyObject, TRUE /*Alertable*/, NULL /*Timeout*/);
        else
        {
            Timeout.QuadPart = -(int64_t)(cMsTimeout - cMsElapsed) * 10000;
            rcNtWait = NtWaitForMultipleObjects(cHandles, &ahHandles[0], WaitAnyObject, TRUE /*Alertable*/, &Timeout);
        }

        /*
         * Process child request.
         */
        if (rcNtWait == STATUS_WAIT_0 + 1 && pThis->hEvtParent != NULL)
        {
            supR3HardNtChildProcessRequest(pThis, enmExpectedRequest, pszWhat);
            SUP_DPRINTF(("supR3HardNtChildWaitFor: Found expected request %d (%s) after %llu ms.\n",
                         enmExpectedRequest, pszWhat, supR3HardenedWinGetMilliTS() - uMsTsStart));
            return; /* Expected request received. */
        }

        /*
         * Process termination?
         */
        if (   (ULONG)rcNtWait - (ULONG)STATUS_WAIT_0           < cHandles
            || (ULONG)rcNtWait - (ULONG)STATUS_ABANDONED_WAIT_0 < cHandles)
            break;

        /*
         * Check sanity.
         */
        if (   rcNtWait != STATUS_TIMEOUT
            && rcNtWait != STATUS_USER_APC
            && rcNtWait != STATUS_ALERTED)
            supR3HardenedWinKillChild(pThis, "supR3HardNtChildWaitFor", rcNtWait,
                                      "NtWaitForMultipleObjects returned %#x waiting for #%d (%s)\n",
                                      rcNtWait, enmExpectedRequest, pszWhat);

        /*
         * Calc elapsed time for the next timeout calculation, checking to see
         * if we've timed out already.
         */
        cMsElapsed = supR3HardenedWinGetMilliTS() - uMsTsStart;
        if (   cMsElapsed > cMsTimeout
            && cMsTimeout != RT_INDEFINITE_WAIT
            && enmExpectedRequest != kSupR3WinChildReq_End)
        {
            if (rcNtWait == STATUS_USER_APC || rcNtWait == STATUS_ALERTED)
                cMsElapsed = cMsTimeout - 1; /* try again */
            else
            {
                /* We timed out. */
                supR3HardenedWinKillChild(pThis, "supR3HardNtChildWaitFor", rcNtWait,
                                          "Timed out after %llu ms waiting for child request #%d (%s).\n",
                                          cMsElapsed, enmExpectedRequest, pszWhat);
            }
        }
    }

    /*
     * Proxy the termination code of the child, if it exited already.
     */
    PROCESS_BASIC_INFORMATION BasicInfo;
    NTSTATUS rcNt1 = NtQueryInformationProcess(pThis->hProcess, ProcessBasicInformation, &BasicInfo, sizeof(BasicInfo), NULL);
    NTSTATUS rcNt2 = STATUS_PENDING;
    NTSTATUS rcNt3 = STATUS_PENDING;
    if (   !NT_SUCCESS(rcNt1)
        || BasicInfo.ExitStatus == STATUS_PENDING)
    {
        rcNt2 = NtTerminateProcess(pThis->hProcess, RTEXITCODE_FAILURE);
        Timeout.QuadPart = NT_SUCCESS(rcNt2) ? -20000000 /* 2 sec */ : -1280000 /* 128 ms */;
        rcNt3 = NtWaitForSingleObject(pThis->hProcess, FALSE /*Alertable*/, NULL /*Timeout*/);
        BasicInfo.ExitStatus = RTEXITCODE_FAILURE;
    }

    SUP_DPRINTF(("supR3HardNtChildWaitFor[%d]: Quitting: ExitCode=%#x (rcNtWait=%#x, rcNt1=%#x, rcNt2=%#x, rcNt3=%#x, %llu ms, %s);\n",
                 pThis->iWhich, BasicInfo.ExitStatus, rcNtWait, rcNt1, rcNt2, rcNt3,
                 supR3HardenedWinGetMilliTS() - uMsTsStart, pszWhat));
    suplibHardenedExit((RTEXITCODE)BasicInfo.ExitStatus);
}


/**
 * Closes full access child thread and process handles, making a harmless
 * duplicate of the process handle first.
 *
 * The hProcess member of the child process data structure will be change to the
 * harmless handle, while the hThread will be set to NULL.
 *
 * @param   pThis               The child process data structure.
 */
static void supR3HardNtChildCloseFullAccessHandles(PSUPR3HARDNTCHILD pThis)
{
    /*
     * The thread handle.
     */
    NTSTATUS rcNt = NtClose(pThis->hThread);
    if (!NT_SUCCESS(rcNt))
        supR3HardenedWinKillChild(pThis, "supR3HardenedWinReSpawn", rcNt, "NtClose(hThread) failed: %#x", rcNt);
    pThis->hThread = NULL;

    /*
     * Duplicate the process handle into a harmless one.
     */
    HANDLE hProcWait;
    ULONG fRights = SYNCHRONIZE | PROCESS_TERMINATE | PROCESS_VM_READ;
    if (g_uNtVerCombined >= SUP_MAKE_NT_VER_SIMPLE(6, 0)) /* Introduced in Vista. */
        fRights |= PROCESS_QUERY_LIMITED_INFORMATION;
    else
        fRights |= PROCESS_QUERY_INFORMATION;
    rcNt = NtDuplicateObject(NtCurrentProcess(), pThis->hProcess,
                             NtCurrentProcess(), &hProcWait,
                             fRights, 0 /*HandleAttributes*/, 0);
    if (rcNt == STATUS_ACCESS_DENIED)
    {
        supR3HardenedError(rcNt, false /*fFatal*/,
                           "supR3HardenedWinDoReSpawn: NtDuplicateObject(,,,,%#x,,) -> %#x, retrying with only %#x...\n",
                           fRights, rcNt, SYNCHRONIZE);
        rcNt = NtDuplicateObject(NtCurrentProcess(), pThis->hProcess,
                                 NtCurrentProcess(), &hProcWait,
                                 SYNCHRONIZE, 0 /*HandleAttributes*/, 0);
    }
    if (!NT_SUCCESS(rcNt))
        supR3HardenedWinKillChild(pThis, "supR3HardenedWinReSpawn", rcNt,
                                  "NtDuplicateObject failed on child process handle: %#x\n", rcNt);
    /*
     * Close the process handle and replace it with the harmless one.
     */
    rcNt = NtClose(pThis->hProcess);
    pThis->hProcess = hProcWait;
    if (!NT_SUCCESS(rcNt))
        supR3HardenedWinKillChild(pThis, "supR3HardenedWinReSpawn", VERR_INVALID_NAME,
                                  "NtClose failed on child process handle: %#x\n", rcNt);
}


/**
 * This restores the child PEB and tweaks a couple of fields before we do the
 * child purification and let the process run normally.
 *
 * @param   pThis               The child process data structure.
 */
static void supR3HardNtChildSanitizePeb(PSUPR3HARDNTCHILD pThis)
{
    /*
     * Make a copy of the pre-execution PEB.
     */
    PEB Peb = pThis->Peb;

#if 0
    /*
     * There should not be any activation context, so if there is, we scratch the memory associated with it.
     */
    int rc = 0;
    if (RT_SUCCESS(rc) && Peb.pShimData && !((uintptr_t)Peb.pShimData & PAGE_OFFSET_MASK))
        rc = supR3HardenedWinScratchChildMemory(hProcess, Peb.pShimData, PAGE_SIZE, "pShimData", pErrInfo);
    if (RT_SUCCESS(rc) && Peb.ActivationContextData && !((uintptr_t)Peb.ActivationContextData & PAGE_OFFSET_MASK))
        rc = supR3HardenedWinScratchChildMemory(hProcess, Peb.ActivationContextData, PAGE_SIZE, "ActivationContextData", pErrInfo);
    if (RT_SUCCESS(rc) && Peb.ProcessAssemblyStorageMap && !((uintptr_t)Peb.ProcessAssemblyStorageMap & PAGE_OFFSET_MASK))
        rc = supR3HardenedWinScratchChildMemory(hProcess, Peb.ProcessAssemblyStorageMap, PAGE_SIZE, "ProcessAssemblyStorageMap", pErrInfo);
    if (RT_SUCCESS(rc) && Peb.SystemDefaultActivationContextData && !((uintptr_t)Peb.SystemDefaultActivationContextData & PAGE_OFFSET_MASK))
        rc = supR3HardenedWinScratchChildMemory(hProcess, Peb.ProcessAssemblyStorageMap, PAGE_SIZE, "SystemDefaultActivationContextData", pErrInfo);
    if (RT_SUCCESS(rc) && Peb.SystemAssemblyStorageMap && !((uintptr_t)Peb.SystemAssemblyStorageMap & PAGE_OFFSET_MASK))
        rc = supR3HardenedWinScratchChildMemory(hProcess, Peb.SystemAssemblyStorageMap, PAGE_SIZE, "SystemAssemblyStorageMap", pErrInfo);
    if (RT_FAILURE(rc))
        return rc;
#endif

    /*
     * Clear compatibility and activation related fields.
     */
    Peb.AppCompatFlags.QuadPart             = 0;
    Peb.AppCompatFlagsUser.QuadPart         = 0;
    Peb.pShimData                           = NULL;
    Peb.AppCompatInfo                       = NULL;
#if 0
    Peb.ActivationContextData               = NULL;
    Peb.ProcessAssemblyStorageMap           = NULL;
    Peb.SystemDefaultActivationContextData  = NULL;
    Peb.SystemAssemblyStorageMap            = NULL;
    /*Peb.Diff0.W6.IsProtectedProcess = 1;*/
#endif

    /*
     * Write back the PEB.
     */
    SIZE_T cbActualMem = pThis->cbPeb;
    NTSTATUS rcNt = NtWriteVirtualMemory(pThis->hProcess, pThis->BasicInfo.PebBaseAddress, &Peb, pThis->cbPeb, &cbActualMem);
    if (!NT_SUCCESS(rcNt))
        supR3HardenedWinKillChild(pThis, "supR3HardNtChildSanitizePeb", rcNt,
                                  "NtWriteVirtualMemory/Peb failed: %#x", rcNt);

}


/**
 * Purifies the child process after very early init has been performed.
 *
 * @param   pThis               The child process data structure.
 */
static void supR3HardNtChildPurify(PSUPR3HARDNTCHILD pThis)
{
    /*
     * We loop until we no longer make any fixes.  This is similar to what
     * we do (or used to do, really) in the fAvastKludge case of
     * supR3HardenedWinInit.  We might be up against asynchronous changes,
     * which we fudge by waiting a short while before earch purification. This
     * is arguably a fragile technique, but it's currently the best we've got.
     * Fortunately, most AVs seems to either favor immediate action on initial
     * load events or (much better for us) later events like kernel32.
     */
    uint64_t uMsTsOuterStart = supR3HardenedWinGetMilliTS();
    uint32_t cMsFudge        = g_fSupAdversaries ? 512 : 256;
    uint32_t cTotalFixes     = 0;
    uint32_t cFixes          = 0; /* (MSC wrongly thinks this maybe used uninitialized) */
    for (uint32_t iLoop = 0; iLoop < 16; iLoop++)
    {
        /*
         * Delay.
         */
        uint32_t cSleeps = 0;
        uint64_t uMsTsStart = supR3HardenedWinGetMilliTS();
        do
        {
            NtYieldExecution();
            LARGE_INTEGER Time;
            Time.QuadPart = -8000000 / 100; /* 8ms in 100ns units, relative time. */
            NtDelayExecution(FALSE, &Time);
            cSleeps++;
        } while (   supR3HardenedWinGetMilliTS() - uMsTsStart <= cMsFudge
                 || cSleeps < 8);
        SUP_DPRINTF(("supR3HardNtChildPurify: Startup delay kludge #1/%u: %u ms, %u sleeps\n",
                     iLoop, supR3HardenedWinGetMilliTS() - uMsTsStart, cSleeps));

        /*
         * Purify.
         */
        cFixes = 0;
        int rc = supHardenedWinVerifyProcess(pThis->hProcess, pThis->hThread, SUPHARDNTVPKIND_CHILD_PURIFICATION,
                                             g_fSupAdversaries & (  SUPHARDNT_ADVERSARY_TRENDMICRO_SAKFILE
                                                                  | SUPHARDNT_ADVERSARY_DIGITAL_GUARDIAN_OLD)
                                             ? SUPHARDNTVP_F_EXEC_ALLOC_REPLACE_WITH_RW : 0,
                                             &cFixes, RTErrInfoInitStatic(&g_ErrInfoStatic));
        if (RT_FAILURE(rc))
            supR3HardenedWinKillChild(pThis, "supR3HardNtChildPurify", rc,
                                      "supHardenedWinVerifyProcess failed with %Rrc: %s", rc, g_ErrInfoStatic.szMsg);
        if (cFixes == 0)
        {
            SUP_DPRINTF(("supR3HardNtChildPurify: Done after %llu ms and %u fixes (loop #%u).\n",
                         supR3HardenedWinGetMilliTS() - uMsTsOuterStart, cTotalFixes, iLoop));
            return; /* We're probably good. */
        }
        cTotalFixes += cFixes;

        if (!g_fSupAdversaries)
            g_fSupAdversaries |= SUPHARDNT_ADVERSARY_UNKNOWN;
        cMsFudge = 512;

        /*
         * Log the KiOpPrefetchPatchCount value if available, hoping it might
         * sched some light on spider38's case.
         */
        ULONG cPatchCount = 0;
        NTSTATUS rcNt = NtQuerySystemInformation(SystemInformation_KiOpPrefetchPatchCount,
                                                 &cPatchCount, sizeof(cPatchCount), NULL);
        if (NT_SUCCESS(rcNt))
            SUP_DPRINTF(("supR3HardNtChildPurify: cFixes=%u g_fSupAdversaries=%#x cPatchCount=%#u\n",
                         cFixes, g_fSupAdversaries, cPatchCount));
        else
            SUP_DPRINTF(("supR3HardNtChildPurify: cFixes=%u g_fSupAdversaries=%#x\n", cFixes, g_fSupAdversaries));
    }

    /*
     * We've given up fixing the child process.  Probably fighting someone
     * that monitors their patches or/and our activities.
     */
    supR3HardenedWinKillChild(pThis, "supR3HardNtChildPurify", VERR_TRY_AGAIN,
                              "Unable to purify child process! After 16 tries over %llu ms, we still %u fix(es) in the last pass.",
                              supR3HardenedWinGetMilliTS() - uMsTsOuterStart, cFixes);
}


/**
 * Sets up the early process init.
 *
 * @param   pThis               The child process data structure.
 */
static void supR3HardNtChildSetUpChildInit(PSUPR3HARDNTCHILD pThis)
{
    uintptr_t const uChildExeAddr = (uintptr_t)pThis->Peb.ImageBaseAddress;

    /*
     * Plant the process parameters.  This ASSUMES the handle inheritance is
     * performed when creating the child process.
     */
    RT_ZERO(pThis->ProcParams);
    pThis->ProcParams.hEvtChild  = pThis->hEvtChild;
    pThis->ProcParams.hEvtParent = pThis->hEvtParent;
    pThis->ProcParams.uNtDllAddr = pThis->uNtDllAddr;
    pThis->ProcParams.enmRequest = kSupR3WinChildReq_Error;
    pThis->ProcParams.rc         = VINF_SUCCESS;

    uintptr_t uChildAddr = uChildExeAddr + ((uintptr_t)&g_ProcParams - (uintptr_t)NtCurrentPeb()->ImageBaseAddress);
    SIZE_T    cbIgnored;
    NTSTATUS  rcNt = NtWriteVirtualMemory(pThis->hProcess, (PVOID)uChildAddr, &pThis->ProcParams,
                                          sizeof(pThis->ProcParams), &cbIgnored);
    if (!NT_SUCCESS(rcNt))
        supR3HardenedWinKillChild(pThis, "supR3HardenedWinSetupChildInit", rcNt,
                                  "NtWriteVirtualMemory(,%p,) failed writing child process parameters: %#x\n", uChildAddr, rcNt);

    /*
     * Locate the LdrInitializeThunk address in the child as well as pristine
     * code bits for it.
     */
    PSUPHNTLDRCACHEENTRY pLdrEntry;
    int rc = supHardNtLdrCacheOpen("ntdll.dll", &pLdrEntry, NULL /*pErrInfo*/);
    if (RT_FAILURE(rc))
        supR3HardenedWinKillChild(pThis, "supR3HardenedWinSetupChildInit", rc,
                                  "supHardNtLdrCacheOpen failed on NTDLL: %Rrc\n", rc);

    uint8_t *pbChildNtDllBits;
    rc = supHardNtLdrCacheEntryGetBits(pLdrEntry, &pbChildNtDllBits, pThis->uNtDllAddr, NULL, NULL, NULL /*pErrInfo*/);
    if (RT_FAILURE(rc))
        supR3HardenedWinKillChild(pThis, "supR3HardenedWinSetupChildInit", rc,
                                  "supHardNtLdrCacheEntryGetBits failed on NTDLL: %Rrc\n", rc);

    RTLDRADDR uLdrInitThunk;
    rc = RTLdrGetSymbolEx(pLdrEntry->hLdrMod, pbChildNtDllBits, pThis->uNtDllAddr, UINT32_MAX,
                          "LdrInitializeThunk", &uLdrInitThunk);
    if (RT_FAILURE(rc))
        supR3HardenedWinKillChild(pThis, "supR3HardenedWinSetupChildInit", rc,
                                  "Error locating LdrInitializeThunk in NTDLL: %Rrc", rc);
    PVOID pvLdrInitThunk = (PVOID)(uintptr_t)uLdrInitThunk;
    SUP_DPRINTF(("supR3HardenedWinSetupChildInit: uLdrInitThunk=%p\n", (uintptr_t)uLdrInitThunk));

    /*
     * Calculate the address of our code in the child process.
     */
    uintptr_t uEarlyProcInitEP = uChildExeAddr + (  (uintptr_t)&supR3HardenedEarlyProcessInitThunk
                                                  - (uintptr_t)NtCurrentPeb()->ImageBaseAddress);

    /*
     * Compose the LdrInitializeThunk replacement bytes.
     * Note! The amount of code we replace here must be less or equal to what
     *       the process verification code ignores.
     */
    uint8_t abNew[16];
    memcpy(abNew, pbChildNtDllBits + ((uintptr_t)uLdrInitThunk - pThis->uNtDllAddr), sizeof(abNew));
#ifdef RT_ARCH_AMD64
    abNew[0] = 0xff;
    abNew[1] = 0x25;
    *(uint32_t *)&abNew[2] = 0;
    *(uint64_t *)&abNew[6] = uEarlyProcInitEP;
#elif defined(RT_ARCH_X86)
    abNew[0] = 0xe9;
    *(uint32_t *)&abNew[1] = uEarlyProcInitEP - ((uint32_t)uLdrInitThunk + 5);
#else
# error "Unsupported arch."
#endif

    /*
     * Install the LdrInitializeThunk replacement code in the child process.
     */
    PVOID   pvProt = pvLdrInitThunk;
    SIZE_T  cbProt = sizeof(abNew);
    ULONG   fOldProt;
    rcNt = NtProtectVirtualMemory(pThis->hProcess, &pvProt, &cbProt, PAGE_EXECUTE_READWRITE, &fOldProt);
    if (!NT_SUCCESS(rcNt))
        supR3HardenedWinKillChild(pThis, "supR3HardenedWinSetupChildInit", rcNt,
                                  "NtProtectVirtualMemory/LdrInitializeThunk failed: %#x", rcNt);

    rcNt = NtWriteVirtualMemory(pThis->hProcess, pvLdrInitThunk, abNew, sizeof(abNew), &cbIgnored);
    if (!NT_SUCCESS(rcNt))
        supR3HardenedWinKillChild(pThis, "supR3HardenedWinSetupChildInit", rcNt,
                                  "NtWriteVirtualMemory/LdrInitializeThunk failed: %#x", rcNt);

    pvProt = pvLdrInitThunk;
    cbProt = sizeof(abNew);
    rcNt = NtProtectVirtualMemory(pThis->hProcess, &pvProt, &cbProt, fOldProt, &fOldProt);
    if (!NT_SUCCESS(rcNt))
        supR3HardenedWinKillChild(pThis, "supR3HardenedWinSetupChildInit", rcNt,
                                  "NtProtectVirtualMemory/LdrInitializeThunk[restore] failed: %#x", rcNt);

    /*
     * Check the sanity of the thread context.
     */
    CONTEXT Ctx;
    RT_ZERO(Ctx);
    Ctx.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS;
    rcNt = NtGetContextThread(pThis->hThread, &Ctx);
    if (NT_SUCCESS(rcNt))
    {
#ifdef RT_ARCH_AMD64
        DWORD64 *pPC = &Ctx.Rip;
#elif defined(RT_ARCH_X86)
        DWORD   *pPC = &Ctx.Eip;
#else
# error "Unsupported arch."
#endif
        supR3HardNtDprintCtx(&Ctx, "supR3HardenedWinSetupChildInit: Initial context:");

        /* Entrypoint for the executable: */
        uintptr_t const uChildMain = uChildExeAddr + (  (uintptr_t)&suplibHardenedWindowsMain
                                                      - (uintptr_t)NtCurrentPeb()->ImageBaseAddress);

        /* NtDll size and the more recent default thread start entrypoint (Vista+?): */
        RTLDRADDR uSystemThreadStart;
        rc = RTLdrGetSymbolEx(pLdrEntry->hLdrMod, pbChildNtDllBits, pThis->uNtDllAddr, UINT32_MAX,
                              "RtlUserThreadStart", &uSystemThreadStart);
        if (RT_FAILURE(rc))
            uSystemThreadStart = 0;

        /* Kernel32 for thread start of older windows version, only XP64/W2K3-64 has an actual
           export for it.  Unfortunately, it is not yet loaded into the child, so we have to
           assume same location as in the parent (safe): */
        PSUPHNTLDRCACHEENTRY pLdrEntryKernel32;
        rc = supHardNtLdrCacheOpen("kernel32.dll", &pLdrEntryKernel32, NULL /*pErrInfo*/);
        if (RT_FAILURE(rc))
            supR3HardenedWinKillChild(pThis, "supR3HardenedWinSetupChildInit", rc,
                                      "supHardNtLdrCacheOpen failed on KERNEL32: %Rrc\n", rc);
        size_t const cbKernel32 = RTLdrSize(pLdrEntryKernel32->hLdrMod);

#ifdef RT_ARCH_AMD64
        if (!uSystemThreadStart)
        {
            rc = RTLdrGetSymbolEx(pLdrEntry->hLdrMod, pbChildNtDllBits, pLdrEntryKernel32->uImageBase, UINT32_MAX,
                                  "BaseProcessStart", &uSystemThreadStart);
            if (RT_FAILURE(rc))
                uSystemThreadStart = 0;
        }
#endif

        bool fUpdateContext = false;

        /* Check if the RIP looks half sane, try correct it if it isn't.
           It should point to RtlUserThreadStart (Vista and later it seem), though only
           tested on win10.  The first parameter is the executable entrypoint, the 2nd
           is probably the PEB.  Before Vista it should point to Kernel32!BaseProcessStart,
           though the symbol is only exported in 5.2/AMD64. */
        if (   (  uSystemThreadStart
                ? *pPC == uSystemThreadStart
                : *pPC - (  pLdrEntryKernel32->uImageBase != ~(uintptr_t)0 ? pLdrEntryKernel32->uImageBase
                          : (uintptr_t)GetModuleHandleW(L"kernel32.dll")) <= cbKernel32)
            || *pPC == uChildMain)
        { }
        else
        {
            SUP_DPRINTF(("Warning! Bogus RIP: %p (uSystemThreadStart=%p; kernel32 %p LB %p; uChildMain=%p)\n",
                         *pPC, uSystemThreadStart, pLdrEntryKernel32->uImageBase, cbKernel32, uChildMain));
            if (uSystemThreadStart)
            {
                SUP_DPRINTF(("Correcting RIP from to %p hoping that it might work...\n", (uintptr_t)uSystemThreadStart));
                *pPC = uSystemThreadStart;
                fUpdateContext = true;
            }
        }
#ifdef RT_ARCH_AMD64
        if (g_uNtVerCombined >= SUP_MAKE_NT_VER_SIMPLE(10, 0)) /* W2K3: CS=33 SS=DS=ES=GS=2b FS=53 */
        {
            if (Ctx.SegDs != 0)
                SUP_DPRINTF(("Warning! Bogus DS: %04x, expected zero\n", Ctx.SegDs));
            if (Ctx.SegEs != 0)
                SUP_DPRINTF(("Warning! Bogus ES: %04x, expected zero\n", Ctx.SegEs));
            if (Ctx.SegFs != 0)
                SUP_DPRINTF(("Warning! Bogus FS: %04x, expected zero\n", Ctx.SegFs));
            if (Ctx.SegGs != 0)
                SUP_DPRINTF(("Warning! Bogus GS: %04x, expected zero\n", Ctx.SegGs));
        }
        if (Ctx.Rcx != uChildMain)
            SUP_DPRINTF(("Warning! Bogus RCX: %016RX64, expected %016RX64\n", Ctx.Rcx, uChildMain));
        if (Ctx.Rdx & PAGE_OFFSET_MASK)
            SUP_DPRINTF(("Warning! Bogus RDX: %016RX64, expected page aligned\n", Ctx.Rdx)); /* PEB */
        if ((Ctx.Rsp & 15) != 8)
            SUP_DPRINTF(("Warning! Misaligned RSP: %016RX64\n", Ctx.Rsp));
#endif
        if (Ctx.SegCs != ASMGetCS())
            SUP_DPRINTF(("Warning! Bogus CS: %04x, expected %04x\n", Ctx.SegCs, ASMGetCS()));
        if (Ctx.SegSs != ASMGetSS())
            SUP_DPRINTF(("Warning! Bogus SS: %04x, expected %04x\n", Ctx.SegSs, ASMGetSS()));
        if (Ctx.Dr0 != 0)
            SUP_DPRINTF(("Warning! Bogus DR0: %016RX64, expected zero\n", Ctx.Dr0));
        if (Ctx.Dr1 != 0)
            SUP_DPRINTF(("Warning! Bogus DR1: %016RX64, expected zero\n", Ctx.Dr1));
        if (Ctx.Dr2 != 0)
            SUP_DPRINTF(("Warning! Bogus DR2: %016RX64, expected zero\n", Ctx.Dr2));
        if (Ctx.Dr3 != 0)
            SUP_DPRINTF(("Warning! Bogus DR3: %016RX64, expected zero\n", Ctx.Dr3));
        if (Ctx.Dr6 != 0)
            SUP_DPRINTF(("Warning! Bogus DR6: %016RX64, expected zero\n", Ctx.Dr6));
        if (Ctx.Dr7 != 0)
        {
            SUP_DPRINTF(("Warning! Bogus DR7: %016RX64, expected zero\n", Ctx.Dr7));
            Ctx.Dr7 = 0;
            fUpdateContext = true;
        }

        if (fUpdateContext)
        {
            rcNt = NtSetContextThread(pThis->hThread, &Ctx);
            if (!NT_SUCCESS(rcNt))
                SUP_DPRINTF(("Error! NtSetContextThread failed: %#x\n", rcNt));
        }
    }

    /* Caller starts child execution. */
    SUP_DPRINTF(("supR3HardenedWinSetupChildInit: Start child.\n"));
}



/**
 * This messes with the child PEB before we trigger the initial image events.
 *
 * @param   pThis               The child process data structure.
 */
static void supR3HardNtChildScrewUpPebForInitialImageEvents(PSUPR3HARDNTCHILD pThis)
{
    /*
     * Not sure if any of the cracker software uses the PEB at this point, but
     * just in case they do make some of the PEB fields a little less useful.
     */
    PEB Peb = pThis->Peb;

    /* Make ImageBaseAddress useless. */
    Peb.ImageBaseAddress = (PVOID)((uintptr_t)Peb.ImageBaseAddress ^ UINT32_C(0x5f139000));
#ifdef RT_ARCH_AMD64
    Peb.ImageBaseAddress = (PVOID)((uintptr_t)Peb.ImageBaseAddress | UINT64_C(0x0313000000000000));
#endif

    /*
     * Write the PEB.
     */
    SIZE_T cbActualMem = pThis->cbPeb;
    NTSTATUS rcNt = NtWriteVirtualMemory(pThis->hProcess, pThis->BasicInfo.PebBaseAddress, &Peb, pThis->cbPeb, &cbActualMem);
    if (!NT_SUCCESS(rcNt))
        supR3HardenedWinKillChild(pThis, "supR3HardNtChildScrewUpPebForInitialImageEvents", rcNt,
                                  "NtWriteVirtualMemory/Peb failed: %#x", rcNt);
}


/**
 * Check if the zero terminated NT unicode string is the path to the given
 * system32 DLL.
 *
 * @returns true if it is, false if not.
 * @param   pUniStr             The zero terminated NT unicode string path.
 * @param   pszName             The name of the system32 DLL.
 */
static bool supR3HardNtIsNamedSystem32Dll(PUNICODE_STRING pUniStr, const char *pszName)
{
    if (pUniStr->Length > g_System32NtPath.UniStr.Length)
    {
        if (memcmp(pUniStr->Buffer, g_System32NtPath.UniStr.Buffer, g_System32NtPath.UniStr.Length) == 0)
        {
            if (pUniStr->Buffer[g_System32NtPath.UniStr.Length / sizeof(WCHAR)] == '\\')
            {
                if (RTUtf16ICmpAscii(&pUniStr->Buffer[g_System32NtPath.UniStr.Length / sizeof(WCHAR) + 1], pszName) == 0)
                    return true;
            }
        }
    }

    return false;
}


/**
 * Worker for supR3HardNtChildGatherData that locates NTDLL in the child
 * process.
 *
 * @param   pThis               The child process data structure.
 */
static void supR3HardNtChildFindNtdll(PSUPR3HARDNTCHILD pThis)
{
    /*
     * Find NTDLL in this process first and take that as a starting point.
     */
    pThis->uNtDllParentAddr = (uintptr_t)GetModuleHandleW(L"ntdll.dll");
    SUPR3HARDENED_ASSERT(pThis->uNtDllParentAddr != 0 && !(pThis->uNtDllParentAddr & PAGE_OFFSET_MASK));
    pThis->uNtDllAddr = pThis->uNtDllParentAddr;

    /*
     * Scan the virtual memory of the child.
     */
    uintptr_t   cbAdvance = 0;
    uintptr_t   uPtrWhere = 0;
    for (uint32_t i = 0; i < 1024; i++)
    {
        /* Query information. */
        SIZE_T                      cbActual = 0;
        MEMORY_BASIC_INFORMATION    MemInfo  = { 0, 0, 0, 0, 0, 0, 0 };
        NTSTATUS rcNt = NtQueryVirtualMemory(pThis->hProcess,
                                             (void const *)uPtrWhere,
                                             MemoryBasicInformation,
                                             &MemInfo,
                                             sizeof(MemInfo),
                                             &cbActual);
        if (!NT_SUCCESS(rcNt))
            break;

        if (   MemInfo.Type == SEC_IMAGE
            || MemInfo.Type == SEC_PROTECTED_IMAGE
            || MemInfo.Type == (SEC_IMAGE | SEC_PROTECTED_IMAGE))
        {
            if (MemInfo.BaseAddress == MemInfo.AllocationBase)
            {
                /* Get the image name. */
                union
                {
                    UNICODE_STRING UniStr;
                    uint8_t abPadding[4096];
                } uBuf;
                rcNt = NtQueryVirtualMemory(pThis->hProcess,
                                            MemInfo.BaseAddress,
                                            MemorySectionName,
                                            &uBuf,
                                            sizeof(uBuf) - sizeof(WCHAR),
                                            &cbActual);
                if (NT_SUCCESS(rcNt))
                {
                    uBuf.UniStr.Buffer[uBuf.UniStr.Length / sizeof(WCHAR)] = '\0';
                    if (supR3HardNtIsNamedSystem32Dll(&uBuf.UniStr, "ntdll.dll"))
                    {
                        pThis->uNtDllAddr = (uintptr_t)MemInfo.AllocationBase;
                        SUP_DPRINTF(("supR3HardNtPuChFindNtdll: uNtDllParentAddr=%p uNtDllChildAddr=%p\n",
                                     pThis->uNtDllParentAddr, pThis->uNtDllAddr));
                        return;
                    }
                }
            }
        }

        /*
         * Advance.
         */
        cbAdvance = MemInfo.RegionSize;
        if (uPtrWhere + cbAdvance <= uPtrWhere)
            break;
        uPtrWhere += MemInfo.RegionSize;
    }

    supR3HardenedWinKillChild(pThis, "supR3HardNtChildFindNtdll", VERR_MODULE_NOT_FOUND, "ntdll.dll not found in child process.");
}


/**
 * Gather child data.
 *
 * @param   pThis               The child process data structure.
 */
static void supR3HardNtChildGatherData(PSUPR3HARDNTCHILD pThis)
{
    /*
     * Basic info.
     */
    ULONG cbActual = 0;
    NTSTATUS rcNt = NtQueryInformationProcess(pThis->hProcess, ProcessBasicInformation,
                                              &pThis->BasicInfo, sizeof(pThis->BasicInfo), &cbActual);
    if (!NT_SUCCESS(rcNt))
        supR3HardenedWinKillChild(pThis, "supR3HardNtChildGatherData", rcNt,
                                  "NtQueryInformationProcess/ProcessBasicInformation failed: %#x", rcNt);

    /*
     * If this is the middle (stub) process, we wish to wait for both child
     * and parent.  So open the parent process.  Not fatal if we cannnot.
     */
    if (pThis->iWhich > 1)
    {
        PROCESS_BASIC_INFORMATION SelfInfo;
        rcNt = NtQueryInformationProcess(NtCurrentProcess(), ProcessBasicInformation, &SelfInfo, sizeof(SelfInfo), &cbActual);
        if (NT_SUCCESS(rcNt))
        {
            OBJECT_ATTRIBUTES ObjAttr;
            InitializeObjectAttributes(&ObjAttr, NULL, 0, NULL /*hRootDir*/, NULL /*pSecDesc*/);

            CLIENT_ID ClientId;
            ClientId.UniqueProcess = (HANDLE)SelfInfo.InheritedFromUniqueProcessId;
            ClientId.UniqueThread  = NULL;

            rcNt = NtOpenProcess(&pThis->hParent, SYNCHRONIZE | PROCESS_QUERY_INFORMATION, &ObjAttr, &ClientId);
#ifdef DEBUG
            SUPR3HARDENED_ASSERT_NT_SUCCESS(rcNt);
#endif
            if (!NT_SUCCESS(rcNt))
            {
                pThis->hParent = NULL;
                SUP_DPRINTF(("supR3HardNtChildGatherData: Failed to open parent process (%#p): %#x\n", ClientId.UniqueProcess, rcNt));
            }
        }

    }

    /*
     * Process environment block.
     */
    if (g_uNtVerCombined < SUP_NT_VER_W2K3)
        pThis->cbPeb = PEB_SIZE_W51;
    else if (g_uNtVerCombined < SUP_NT_VER_VISTA)
        pThis->cbPeb = PEB_SIZE_W52;
    else if (g_uNtVerCombined < SUP_NT_VER_W70)
        pThis->cbPeb = PEB_SIZE_W6;
    else if (g_uNtVerCombined < SUP_NT_VER_W80)
        pThis->cbPeb = PEB_SIZE_W7;
    else if (g_uNtVerCombined < SUP_NT_VER_W81)
        pThis->cbPeb = PEB_SIZE_W80;
    else
        pThis->cbPeb = PEB_SIZE_W81;

    SUP_DPRINTF(("supR3HardNtChildGatherData: PebBaseAddress=%p cbPeb=%#x\n",
                 pThis->BasicInfo.PebBaseAddress, pThis->cbPeb));

    SIZE_T cbActualMem;
    RT_ZERO(pThis->Peb);
    rcNt = NtReadVirtualMemory(pThis->hProcess, pThis->BasicInfo.PebBaseAddress, &pThis->Peb, sizeof(pThis->Peb), &cbActualMem);
    if (!NT_SUCCESS(rcNt))
        supR3HardenedWinKillChild(pThis, "supR3HardNtChildGatherData", rcNt,
                                  "NtReadVirtualMemory/Peb failed: %#x", rcNt);

    /*
     * Locate NtDll.
     */
    supR3HardNtChildFindNtdll(pThis);
}


/**
 * Does the actually respawning.
 *
 * @returns Never, will call exit or raise fatal error.
 * @param   iWhich              Which respawn we're to check for, 1 being the
 *                              first one, and 2 the second and final.
 */
static DECL_NO_RETURN(void) supR3HardenedWinDoReSpawn(int iWhich)
{
    NTSTATUS                        rcNt;
    PPEB                            pPeb              = NtCurrentPeb();
    PRTL_USER_PROCESS_PARAMETERS    pParentProcParams = pPeb->ProcessParameters;

    SUPR3HARDENED_ASSERT(g_cSuplibHardenedWindowsMainCalls == 1);

    /*
     * Init the child process data structure, creating the child communication
     * event sempahores.
     */
    SUPR3HARDNTCHILD This;
    RT_ZERO(This);
    This.iWhich = iWhich;

    OBJECT_ATTRIBUTES ObjAttrs;
    This.hEvtChild = NULL;
    InitializeObjectAttributes(&ObjAttrs, NULL /*pName*/, OBJ_INHERIT, NULL /*hRootDir*/, NULL /*pSecDesc*/);
    SUPR3HARDENED_ASSERT_NT_SUCCESS(NtCreateEvent(&This.hEvtChild, EVENT_ALL_ACCESS, &ObjAttrs, SynchronizationEvent, FALSE));

    This.hEvtParent = NULL;
    InitializeObjectAttributes(&ObjAttrs, NULL /*pName*/, OBJ_INHERIT, NULL /*hRootDir*/, NULL /*pSecDesc*/);
    SUPR3HARDENED_ASSERT_NT_SUCCESS(NtCreateEvent(&This.hEvtParent, EVENT_ALL_ACCESS, &ObjAttrs, SynchronizationEvent, FALSE));

    /*
     * Set up security descriptors.
     */
    SECURITY_ATTRIBUTES ProcessSecAttrs;
    MYSECURITYCLEANUP   ProcessSecAttrsCleanup;
    supR3HardNtChildInitSecAttrs(&ProcessSecAttrs, &ProcessSecAttrsCleanup, true /*fProcess*/);

    SECURITY_ATTRIBUTES ThreadSecAttrs;
    MYSECURITYCLEANUP   ThreadSecAttrsCleanup;
    supR3HardNtChildInitSecAttrs(&ThreadSecAttrs, &ThreadSecAttrsCleanup, false /*fProcess*/);

#if 1
    /*
     * Configure the startup info and creation flags.
     */
    DWORD dwCreationFlags = CREATE_SUSPENDED;

    STARTUPINFOEXW SiEx;
    suplibHardenedMemSet(&SiEx, 0, sizeof(SiEx));
    if (1)
        SiEx.StartupInfo.cb = sizeof(SiEx.StartupInfo);
    else
    {
        SiEx.StartupInfo.cb = sizeof(SiEx);
        dwCreationFlags |= EXTENDED_STARTUPINFO_PRESENT;
        /** @todo experiment with protected process stuff later on. */
    }

    SiEx.StartupInfo.dwFlags |= pParentProcParams->WindowFlags & STARTF_USESHOWWINDOW;
    SiEx.StartupInfo.wShowWindow = (WORD)pParentProcParams->ShowWindowFlags;

    SiEx.StartupInfo.dwFlags |= STARTF_USESTDHANDLES;
    SiEx.StartupInfo.hStdInput  = pParentProcParams->StandardInput;
    SiEx.StartupInfo.hStdOutput = pParentProcParams->StandardOutput;
    SiEx.StartupInfo.hStdError  = pParentProcParams->StandardError;

    /*
     * Construct the command line and launch the process.
     */
    PRTUTF16 pwszCmdLine = supR3HardNtChildConstructCmdLine(NULL, iWhich);

    supR3HardenedWinEnableThreadCreation();
    PROCESS_INFORMATION ProcessInfoW32 = { NULL, NULL, 0, 0 };
    if (!CreateProcessW(g_wszSupLibHardenedExePath,
                        pwszCmdLine,
                        &ProcessSecAttrs,
                        &ThreadSecAttrs,
                        TRUE /*fInheritHandles*/,
                        dwCreationFlags,
                        NULL /*pwszzEnvironment*/,
                        NULL /*pwszCurDir*/,
                        &SiEx.StartupInfo,
                        &ProcessInfoW32))
        supR3HardenedFatalMsg("supR3HardenedWinReSpawn", kSupInitOp_Misc, VERR_INVALID_NAME,
                              "Error relaunching VirtualBox VM process: %u\n"
                              "Command line: '%ls'",
                              RtlGetLastWin32Error(), pwszCmdLine);
    supR3HardenedWinDisableThreadCreation();

    SUP_DPRINTF(("supR3HardenedWinDoReSpawn(%d): New child %x.%x [kernel32].\n",
                 iWhich, ProcessInfoW32.dwProcessId, ProcessInfoW32.dwThreadId));
    This.hProcess = ProcessInfoW32.hProcess;
    This.hThread  = ProcessInfoW32.hThread;

#else

    /*
     * Construct the process parameters.
     */
    UNICODE_STRING W32ImageName;
    W32ImageName.Buffer = g_wszSupLibHardenedExePath; /* Yes the windows name for the process parameters. */
    W32ImageName.Length = (USHORT)RTUtf16Len(g_wszSupLibHardenedExePath) * sizeof(WCHAR);
    W32ImageName.MaximumLength = W32ImageName.Length + sizeof(WCHAR);

    UNICODE_STRING CmdLine;
    supR3HardNtChildConstructCmdLine(&CmdLine, iWhich);

    PRTL_USER_PROCESS_PARAMETERS pProcParams = NULL;
    SUPR3HARDENED_ASSERT_NT_SUCCESS(RtlCreateProcessParameters(&pProcParams,
                                                               &W32ImageName,
                                                               NULL /* DllPath - inherit from this process */,
                                                               NULL /* CurrentDirectory - inherit from this process */,
                                                               &CmdLine,
                                                               NULL /* Environment - inherit from this process */,
                                                               NULL /* WindowsTitle - none */,
                                                               NULL /* DesktopTitle - none. */,
                                                               NULL /* ShellInfo - none. */,
                                                               NULL /* RuntimeInfo - none (byte array for MSVCRT file info) */)
                                    );

    /** @todo this doesn't work. :-( */
    pProcParams->ConsoleHandle  = pParentProcParams->ConsoleHandle;
    pProcParams->ConsoleFlags   = pParentProcParams->ConsoleFlags;
    pProcParams->StandardInput  = pParentProcParams->StandardInput;
    pProcParams->StandardOutput = pParentProcParams->StandardOutput;
    pProcParams->StandardError  = pParentProcParams->StandardError;

    RTL_USER_PROCESS_INFORMATION ProcessInfoNt = { sizeof(ProcessInfoNt) };
    rcNt = RtlCreateUserProcess(&g_SupLibHardenedExeNtPath.UniStr,
                                OBJ_INHERIT | OBJ_CASE_INSENSITIVE /*Attributes*/,
                                pProcParams,
                                NULL, //&ProcessSecAttrs,
                                NULL, //&ThreadSecAttrs,
                                NtCurrentProcess() /* ParentProcess */,
                                FALSE /*fInheritHandles*/,
                                NULL /* DebugPort */,
                                NULL /* ExceptionPort */,
                                &ProcessInfoNt);
    if (!NT_SUCCESS(rcNt))
        supR3HardenedFatalMsg("supR3HardenedWinReSpawn", kSupInitOp_Misc, VERR_INVALID_NAME,
                              "Error relaunching VirtualBox VM process: %#x\n"
                              "Command line: '%ls'",
                              rcNt, CmdLine.Buffer);

    SUP_DPRINTF(("supR3HardenedWinDoReSpawn(%d): New child %x.%x [ntdll].\n",
                 iWhich, ProcessInfo.ClientId.UniqueProcess, ProcessInfo.ClientId.UniqueThread));
    RtlDestroyProcessParameters(pProcParams);

    This.hProcess = ProcessInfoNt.ProcessHandle;
    This.hThread  = ProcessInfoNt.ThreadHandle;
#endif

#ifndef VBOX_WITHOUT_DEBUGGER_CHECKS
    /*
     * Apply anti debugger notification trick to the thread.  (Also done in
     * supR3HardenedWinInit.)  This may fail with STATUS_ACCESS_DENIED and
     * maybe other errors.  (Unfortunately, recent (SEP 12.1) of symantec's
     * sysplant.sys driver will cause process deadlocks and a shutdown/reboot
     * denial of service problem if we hide the initial thread, so we postpone
     * this action if we've detected SEP.)
     */
    if (!(g_fSupAdversaries & (SUPHARDNT_ADVERSARY_SYMANTEC_SYSPLANT | SUPHARDNT_ADVERSARY_SYMANTEC_N360)))
    {
        rcNt = NtSetInformationThread(This.hThread, ThreadHideFromDebugger, NULL, 0);
        if (!NT_SUCCESS(rcNt))
            SUP_DPRINTF(("supR3HardenedWinReSpawn: NtSetInformationThread/ThreadHideFromDebugger failed: %#x (harmless)\n", rcNt));
    }
#endif

    /*
     * Perform very early child initialization.
     */
    supR3HardNtChildGatherData(&This);
    supR3HardNtChildScrewUpPebForInitialImageEvents(&This);
    supR3HardNtChildSetUpChildInit(&This);

    ULONG cSuspendCount = 0;
    rcNt = NtResumeThread(This.hThread, &cSuspendCount);
    if (!NT_SUCCESS(rcNt))
        supR3HardenedWinKillChild(&This, "supR3HardenedWinDoReSpawn", rcNt, "NtResumeThread failed: %#x", rcNt);

    /*
     * Santizie the pre-NTDLL child when it's ready.
     *
     * AV software and other things injecting themselves into the embryonic
     * and budding process to intercept API calls and what not.  Unfortunately
     * this is also the behavior of viruses, malware and other unfriendly
     * software, so we won't stand for it.  AV software can scan our image
     * as they are loaded via kernel hooks, that's sufficient.  No need for
     * patching half of NTDLL or messing with the import table of the
     * process executable.
     */
    supR3HardNtChildWaitFor(&This, kSupR3WinChildReq_PurifyChildAndCloseHandles, 2000 /*ms*/, "PurifyChildAndCloseHandles");
    supR3HardNtChildPurify(&This);
    supR3HardNtChildSanitizePeb(&This);

    /*
     * Close the unrestricted access handles.  Since we need to wait on the
     * child process, we'll reopen the process with limited access before doing
     * away with the process handle returned by CreateProcess.
     */
    supR3HardNtChildCloseFullAccessHandles(&This);

    /*
     * Signal the child that we've closed the unrestricted handles and it can
     * safely try open the driver.
     */
    rcNt = NtSetEvent(This.hEvtChild, NULL);
    if (!NT_SUCCESS(rcNt))
        supR3HardenedWinKillChild(&This, "supR3HardenedWinReSpawn", VERR_INVALID_NAME,
                                  "NtSetEvent failed on child process handle: %#x\n", rcNt);

    /*
     * Ditch the loader cache so we don't sit on too much memory while waiting.
     */
    supR3HardenedWinFlushLoaderCache();
    supR3HardenedWinCompactHeaps();

    /*
     * Enable thread creation at this point so Ctrl-C and Ctrl-Break can be processed.
     */
    supR3HardenedWinEnableThreadCreation();

    /*
     * Wait for the child to get to suplibHardenedWindowsMain so we can close the handles.
     */
    supR3HardNtChildWaitFor(&This, kSupR3WinChildReq_CloseEvents, 60000 /*ms*/, "CloseEvents");

    NtClose(This.hEvtChild);
    NtClose(This.hEvtParent);
    This.hEvtChild  = NULL;
    This.hEvtParent = NULL;

    /*
     * Wait for the process to terminate.
     */
    supR3HardNtChildWaitFor(&This, kSupR3WinChildReq_End, RT_INDEFINITE_WAIT, "the end");
    supR3HardenedFatal("supR3HardenedWinDoReSpawn: supR3HardNtChildWaitFor unexpectedly returned!\n");
    /* not reached*/
}


/**
 * Logs the content of the given object directory.
 *
 * @returns true if it exists, false if not.
 * @param   pszDir             The path of the directory to log (ASCII).
 */
static void supR3HardenedWinLogObjDir(const char *pszDir)
{
    /*
     * Open the driver object directory.
     */
    RTUTF16 wszDir[128];
    int rc = RTUtf16CopyAscii(wszDir, RT_ELEMENTS(wszDir), pszDir);
    if (RT_FAILURE(rc))
    {
        SUP_DPRINTF(("supR3HardenedWinLogObjDir: RTUtf16CopyAscii -> %Rrc on '%s'\n", rc, pszDir));
        return;
    }

    UNICODE_STRING NtDirName;
    NtDirName.Buffer = (WCHAR *)wszDir;
    NtDirName.Length = (USHORT)(RTUtf16Len(wszDir) * sizeof(WCHAR));
    NtDirName.MaximumLength = NtDirName.Length + sizeof(WCHAR);

    OBJECT_ATTRIBUTES ObjAttr;
    InitializeObjectAttributes(&ObjAttr, &NtDirName, OBJ_CASE_INSENSITIVE, NULL /*hRootDir*/, NULL /*pSecDesc*/);

    HANDLE hDir;
    NTSTATUS rcNt = NtOpenDirectoryObject(&hDir, DIRECTORY_QUERY | FILE_LIST_DIRECTORY, &ObjAttr);
    SUP_DPRINTF(("supR3HardenedWinLogObjDir: %ls => %#x\n", wszDir, rcNt));
    if (!NT_SUCCESS(rcNt))
        return;

    /*
     * Enumerate it, looking for the driver.
     */
    ULONG uObjDirCtx = 0;
    for (;;)
    {
        uint32_t    abBuffer[_64K + _1K];
        ULONG       cbActual;
        rcNt = NtQueryDirectoryObject(hDir,
                                      abBuffer,
                                      sizeof(abBuffer) - 4, /* minus four for string terminator space. */
                                      FALSE /*ReturnSingleEntry */,
                                      FALSE /*RestartScan*/,
                                      &uObjDirCtx,
                                      &cbActual);
        if (!NT_SUCCESS(rcNt) || cbActual < sizeof(OBJECT_DIRECTORY_INFORMATION))
        {
            SUP_DPRINTF(("supR3HardenedWinLogObjDir: NtQueryDirectoryObject => rcNt=%#x cbActual=%#x\n", rcNt, cbActual));
            break;
        }

        POBJECT_DIRECTORY_INFORMATION pObjDir = (POBJECT_DIRECTORY_INFORMATION)abBuffer;
        while (pObjDir->Name.Length != 0)
        {
            SUP_DPRINTF(("  %.*ls  %.*ls\n",
                         pObjDir->TypeName.Length / sizeof(WCHAR), pObjDir->TypeName.Buffer,
                         pObjDir->Name.Length / sizeof(WCHAR), pObjDir->Name.Buffer));

            /* Next directory entry. */
            pObjDir++;
        }
    }

    /*
     * Clean up and return.
     */
    NtClose(hDir);
}


/**
 * Tries to open VBoxDrvErrorInfo and read extra error info from it.
 *
 * @returns pszErrorInfo.
 * @param   pszErrorInfo        The destination buffer.  Will always be
 *                              terminated.
 * @param   cbErrorInfo         The size of the destination buffer.
 * @param   pszPrefix           What to prefix the error info with, if we got
 *                              anything.
 */
DECLHIDDEN(char *) supR3HardenedWinReadErrorInfoDevice(char *pszErrorInfo, size_t cbErrorInfo, const char *pszPrefix)
{
    RT_BZERO(pszErrorInfo, cbErrorInfo);

    /*
     * Try open the device.
     */
    HANDLE              hFile  = RTNT_INVALID_HANDLE_VALUE;
    IO_STATUS_BLOCK     Ios    = RTNT_IO_STATUS_BLOCK_INITIALIZER;
    UNICODE_STRING      NtName = RTNT_CONSTANT_UNISTR(SUPDRV_NT_DEVICE_NAME_ERROR_INFO);
    OBJECT_ATTRIBUTES   ObjAttr;
    InitializeObjectAttributes(&ObjAttr, &NtName, OBJ_CASE_INSENSITIVE, NULL /*hRootDir*/, NULL /*pSecDesc*/);
    NTSTATUS rcNt = NtCreateFile(&hFile,
                                 GENERIC_READ, /* No SYNCHRONIZE. */
                                 &ObjAttr,
                                 &Ios,
                                 NULL /* Allocation Size*/,
                                 FILE_ATTRIBUTE_NORMAL,
                                 FILE_SHARE_READ | FILE_SHARE_WRITE,
                                 FILE_OPEN,
                                 FILE_NON_DIRECTORY_FILE, /* No FILE_SYNCHRONOUS_IO_NONALERT. */
                                 NULL /*EaBuffer*/,
                                 0 /*EaLength*/);
    if (NT_SUCCESS(rcNt))
        rcNt = Ios.Status;
    if (NT_SUCCESS(rcNt))
    {
        /*
         * Try read error info.
         */
        size_t cchPrefix = strlen(pszPrefix);
        if (cchPrefix + 3 < cbErrorInfo)
        {
            LARGE_INTEGER offRead;
            offRead.QuadPart = 0;
            rcNt = NtReadFile(hFile, NULL /*hEvent*/, NULL /*ApcRoutine*/, NULL /*ApcContext*/, &Ios,
                              &pszErrorInfo[cchPrefix], (ULONG)(cbErrorInfo - cchPrefix - 1), &offRead, NULL);
            if (NT_SUCCESS(rcNt) && NT_SUCCESS(Ios.Status) && Ios.Information > 0)
            {
                memcpy(pszErrorInfo, pszPrefix, cchPrefix);
                pszErrorInfo[RT_MIN(cbErrorInfo - 1, cchPrefix + Ios.Information)] = '\0';
                SUP_DPRINTF(("supR3HardenedWinReadErrorInfoDevice: '%s'", &pszErrorInfo[cchPrefix]));
            }
            else
            {
                *pszErrorInfo = '\0';
                if (rcNt != STATUS_END_OF_FILE || Ios.Status != STATUS_END_OF_FILE)
                    SUP_DPRINTF(("supR3HardenedWinReadErrorInfoDevice: NtReadFile -> %#x / %#x / %p\n",
                                 rcNt, Ios.Status, Ios.Information));
            }
        }
        else
            RTStrCopy(pszErrorInfo, cbErrorInfo, "error info buffer too small");
        NtClose(hFile);
    }
    else
        SUP_DPRINTF(("supR3HardenedWinReadErrorInfoDevice: NtCreateFile -> %#x\n", rcNt));

    return pszErrorInfo;
}



/**
 * Checks if the driver exists.
 *
 * This checks whether the driver is present in the /Driver object directory.
 * Drivers being initialized or terminated will have an object there
 * before/after their devices nodes are created/deleted.
 *
 * @returns true if it exists, false if not.
 * @param   pszDriver           The driver name.
 */
static bool supR3HardenedWinDriverExists(const char *pszDriver)
{
    /*
     * Open the driver object directory.
     */
    UNICODE_STRING NtDirName = RTNT_CONSTANT_UNISTR(L"\\Driver");

    OBJECT_ATTRIBUTES ObjAttr;
    InitializeObjectAttributes(&ObjAttr, &NtDirName, OBJ_CASE_INSENSITIVE, NULL /*hRootDir*/, NULL /*pSecDesc*/);

    HANDLE hDir;
    NTSTATUS rcNt = NtOpenDirectoryObject(&hDir, DIRECTORY_QUERY | FILE_LIST_DIRECTORY, &ObjAttr);
#ifdef VBOX_STRICT
    SUPR3HARDENED_ASSERT_NT_SUCCESS(rcNt);
#endif
    if (!NT_SUCCESS(rcNt))
        return true;

    /*
     * Enumerate it, looking for the driver.
     */
    bool  fFound = true;
    ULONG uObjDirCtx = 0;
    do
    {
        uint32_t    abBuffer[_64K + _1K];
        ULONG       cbActual;
        rcNt = NtQueryDirectoryObject(hDir,
                                      abBuffer,
                                      sizeof(abBuffer) - 4, /* minus four for string terminator space. */
                                      FALSE /*ReturnSingleEntry */,
                                      FALSE /*RestartScan*/,
                                      &uObjDirCtx,
                                      &cbActual);
        if (!NT_SUCCESS(rcNt) || cbActual < sizeof(OBJECT_DIRECTORY_INFORMATION))
            break;

        POBJECT_DIRECTORY_INFORMATION pObjDir = (POBJECT_DIRECTORY_INFORMATION)abBuffer;
        while (pObjDir->Name.Length != 0)
        {
            WCHAR wcSaved = pObjDir->Name.Buffer[pObjDir->Name.Length / sizeof(WCHAR)];
            pObjDir->Name.Buffer[pObjDir->Name.Length / sizeof(WCHAR)] = '\0';
            if (   pObjDir->Name.Length > 1
                && RTUtf16ICmpAscii(pObjDir->Name.Buffer, pszDriver) == 0)
            {
                fFound = true;
                break;
            }
            pObjDir->Name.Buffer[pObjDir->Name.Length / sizeof(WCHAR)] = wcSaved;

            /* Next directory entry. */
            pObjDir++;
        }
    } while (!fFound);

    /*
     * Clean up and return.
     */
    NtClose(hDir);

    return fFound;
}


/**
 * Open the stub device before the 2nd respawn.
 */
static void supR3HardenedWinOpenStubDevice(void)
{
    if (g_fSupStubOpened)
        return;

    /*
     * Retry if we think driver might still be initializing (STATUS_NO_SUCH_DEVICE + \Drivers\VBoxDrv).
     */
    static const WCHAR  s_wszName[] = SUPDRV_NT_DEVICE_NAME_STUB;
    uint64_t const      uMsTsStart = supR3HardenedWinGetMilliTS();
    NTSTATUS            rcNt;
    uint32_t            iTry;

    for (iTry = 0;; iTry++)
    {
        HANDLE              hFile = RTNT_INVALID_HANDLE_VALUE;
        IO_STATUS_BLOCK     Ios   = RTNT_IO_STATUS_BLOCK_INITIALIZER;

        UNICODE_STRING      NtName;
        NtName.Buffer        = (PWSTR)s_wszName;
        NtName.Length        = sizeof(s_wszName) - sizeof(WCHAR);
        NtName.MaximumLength = sizeof(s_wszName);

        OBJECT_ATTRIBUTES   ObjAttr;
        InitializeObjectAttributes(&ObjAttr, &NtName, OBJ_CASE_INSENSITIVE, NULL /*hRootDir*/, NULL /*pSecDesc*/);

        rcNt = NtCreateFile(&hFile,
                            GENERIC_READ | GENERIC_WRITE, /* No SYNCHRONIZE. */
                            &ObjAttr,
                            &Ios,
                            NULL /* Allocation Size*/,
                            FILE_ATTRIBUTE_NORMAL,
                            FILE_SHARE_READ | FILE_SHARE_WRITE,
                            FILE_OPEN,
                            FILE_NON_DIRECTORY_FILE, /* No FILE_SYNCHRONOUS_IO_NONALERT. */
                            NULL /*EaBuffer*/,
                            0 /*EaLength*/);
        if (NT_SUCCESS(rcNt))
            rcNt = Ios.Status;

        /* The STATUS_NO_SUCH_DEVICE might be returned if the device is not
           completely initialized.  Delay a little bit and try again. */
        if (rcNt != STATUS_NO_SUCH_DEVICE)
            break;
        if (iTry > 0 && supR3HardenedWinGetMilliTS() - uMsTsStart > 5000)  /* 5 sec, at least two tries */
            break;
        if (!supR3HardenedWinDriverExists("VBoxDrv"))
        {
            /** @todo Consider starting the VBoxdrv.sys service. Requires 2nd process
             *        though, rather complicated actually as CreateProcess causes all
             *        kind of things to happen to this process which would make it hard to
             *        pass the process verification tests... :-/ */
            break;
        }

        LARGE_INTEGER Time;
        if (iTry < 8)
            Time.QuadPart = -1000000 / 100; /* 1ms in 100ns units, relative time. */
        else
            Time.QuadPart = -32000000 / 100; /* 32ms in 100ns units, relative time. */
        NtDelayExecution(TRUE, &Time);
    }

    if (NT_SUCCESS(rcNt))
        g_fSupStubOpened = true;
    else
    {
        /*
         * Report trouble (fatal).  For some errors codes we try gather some
         * extra information that goes into VBoxStartup.log so that we stand a
         * better chance resolving the issue.
         */
        char szErrorInfo[16384];
        int rc = VERR_OPEN_FAILED;
        if (SUP_NT_STATUS_IS_VBOX(rcNt)) /* See VBoxDrvNtErr2NtStatus. */
        {
            rc = SUP_NT_STATUS_TO_VBOX(rcNt);

            /*
             * \Windows\ApiPort open trouble.  So far only
             * STATUS_OBJECT_TYPE_MISMATCH has been observed.
             */
            if (rc == VERR_SUPDRV_APIPORT_OPEN_ERROR)
            {
                SUP_DPRINTF(("Error opening VBoxDrvStub: VERR_SUPDRV_APIPORT_OPEN_ERROR\n"));

                uint32_t uSessionId = NtCurrentPeb()->SessionId;
                SUP_DPRINTF(("  SessionID=%#x\n", uSessionId));
                char szDir[64];
                if (uSessionId == 0)
                    RTStrCopy(szDir, sizeof(szDir), "\\Windows");
                else
                {
                    RTStrPrintf(szDir, sizeof(szDir), "\\Sessions\\%u\\Windows", uSessionId);
                    supR3HardenedWinLogObjDir(szDir);
                }
                supR3HardenedWinLogObjDir("\\Windows");
                supR3HardenedWinLogObjDir("\\Sessions");

                supR3HardenedFatalMsg("supR3HardenedWinReSpawn", kSupInitOp_Misc, rc,
                                      "NtCreateFile(%ls) failed: VERR_SUPDRV_APIPORT_OPEN_ERROR\n"
                                      "\n"
                                      "Error getting %s\\ApiPort in the driver from vboxdrv.\n"
                                      "\n"
                                      "Could be due to security software is redirecting access to it, so please include full "
                                      "details of such software in a bug report. VBoxStartup.log may contain details important "
                                      "to resolving the issue.%s"
                                      , s_wszName, szDir,
                                      supR3HardenedWinReadErrorInfoDevice(szErrorInfo, sizeof(szErrorInfo),
                                                                          "\n\nVBoxDrvStub error: "));
            }

            /*
             * Generic VBox failure message.
             */
            supR3HardenedFatalMsg("supR3HardenedWinReSpawn", kSupInitOp_Driver, rc,
                                  "NtCreateFile(%ls) failed: %Rrc (rcNt=%#x)%s", s_wszName, rc, rcNt,
                                  supR3HardenedWinReadErrorInfoDevice(szErrorInfo, sizeof(szErrorInfo),
                                                                      "\nVBoxDrvStub error: "));
        }
        else
        {
            const char *pszDefine;
            switch (rcNt)
            {
                case STATUS_NO_SUCH_DEVICE:         pszDefine = " STATUS_NO_SUCH_DEVICE"; break;
                case STATUS_OBJECT_NAME_NOT_FOUND:  pszDefine = " STATUS_OBJECT_NAME_NOT_FOUND"; break;
                case STATUS_ACCESS_DENIED:          pszDefine = " STATUS_ACCESS_DENIED"; break;
                case STATUS_TRUST_FAILURE:          pszDefine = " STATUS_TRUST_FAILURE"; break;
                default:                            pszDefine = ""; break;
            }

            /*
             * Problems opening the device is generally due to driver load/
             * unload issues.  Check whether the driver is loaded and make
             * suggestions accordingly.
             */
/** @todo don't fail during early init, wait till later and try load the driver if missing or at least query the service manager for additional information. */
            if (   rcNt == STATUS_NO_SUCH_DEVICE
                || rcNt == STATUS_OBJECT_NAME_NOT_FOUND)
            {
                SUP_DPRINTF(("Error opening VBoxDrvStub: %s\n", pszDefine));
                if (supR3HardenedWinDriverExists("VBoxDrv"))
                    supR3HardenedFatalMsg("supR3HardenedWinReSpawn", kSupInitOp_Driver, VERR_OPEN_FAILED,
                                          "NtCreateFile(%ls) failed: %#x%s (%u retries)\n"
                                          "\n"
                                          "Driver is probably stuck stopping/starting. Try 'sc.exe query vboxdrv' to get more "
                                          "information about its state. Rebooting may actually help.%s"
                                          , s_wszName, rcNt, pszDefine, iTry,
                                          supR3HardenedWinReadErrorInfoDevice(szErrorInfo, sizeof(szErrorInfo),
                                                                              "\nVBoxDrvStub error: "));
                else
                    supR3HardenedFatalMsg("supR3HardenedWinReSpawn", kSupInitOp_Driver, VERR_OPEN_FAILED,
                                          "NtCreateFile(%ls) failed: %#x%s (%u retries)\n"
                                          "\n"
                                          "Driver is does not appear to be loaded. Try 'sc.exe start vboxdrv', reinstall "
                                          "VirtualBox or reboot.%s"
                                          , s_wszName, rcNt, pszDefine, iTry,
                                          supR3HardenedWinReadErrorInfoDevice(szErrorInfo, sizeof(szErrorInfo),
                                                                              "\nVBoxDrvStub error: "));
            }

            /* Generic NT failure message. */
            supR3HardenedFatalMsg("supR3HardenedWinReSpawn", kSupInitOp_Driver, VERR_OPEN_FAILED,
                                  "NtCreateFile(%ls) failed: %#x%s (%u retries)%s",
                                  s_wszName, rcNt, pszDefine, iTry,
                                  supR3HardenedWinReadErrorInfoDevice(szErrorInfo, sizeof(szErrorInfo),
                                                                      "\nVBoxDrvStub error: "));
        }
    }
}


/**
 * Called by the main code if supR3HardenedWinIsReSpawnNeeded returns @c true.
 *
 * @returns Program exit code.
 */
DECLHIDDEN(int) supR3HardenedWinReSpawn(int iWhich)
{
    /*
     * Before the 2nd respawn we set up a child protection deal with the
     * support driver via /Devices/VBoxDrvStub.  (We tried to do this
     * during the early init, but in case we had trouble accessing vboxdrv we
     * retry it here where we have kernel32.dll and others to pull in for
     * better diagnostics.)
     */
    if (iWhich == 2)
        supR3HardenedWinOpenStubDevice();

    /*
     * Make sure we're alone in the stub process before creating the VM process
     * and that there aren't any debuggers attached.
     */
    if (iWhich == 2)
    {
        int rc = supHardNtVpDebugger(NtCurrentProcess(), RTErrInfoInitStatic(&g_ErrInfoStatic));
        if (RT_SUCCESS(rc))
            rc = supHardNtVpThread(NtCurrentProcess(), NtCurrentThread(), RTErrInfoInitStatic(&g_ErrInfoStatic));
        if (RT_FAILURE(rc))
            supR3HardenedFatalMsg("supR3HardenedWinReSpawn", kSupInitOp_Integrity, rc, "%s", g_ErrInfoStatic.szMsg);
    }


    /*
     * Respawn the process with kernel protection for the new process.
     */
    supR3HardenedWinDoReSpawn(iWhich);
    /* not reached! */
}


/**
 * Checks if re-spawning is required, replacing the respawn argument if not.
 *
 * @returns true if required, false if not. In the latter case, the first
 *          argument in the vector is replaced.
 * @param   iWhich              Which respawn we're to check for, 1 being the
 *                              first one, and 2 the second and final.
 * @param   cArgs               The number of arguments.
 * @param   papszArgs           Pointer to the argument vector.
 */
DECLHIDDEN(bool) supR3HardenedWinIsReSpawnNeeded(int iWhich, int cArgs, char **papszArgs)
{
    SUPR3HARDENED_ASSERT(g_cSuplibHardenedWindowsMainCalls == 1);
    SUPR3HARDENED_ASSERT(iWhich == 1 || iWhich == 2);

    if (cArgs < 1)
        return true;

    if (suplibHardenedStrCmp(papszArgs[0], SUPR3_RESPAWN_1_ARG0) == 0)
    {
        if (iWhich > 1)
            return true;
    }
    else if (suplibHardenedStrCmp(papszArgs[0], SUPR3_RESPAWN_2_ARG0) == 0)
    {
        if (iWhich < 2)
            return false;
    }
    else
        return true;

    /* Replace the argument. */
    papszArgs[0] = g_szSupLibHardenedExePath;
    return false;
}


/**
 * Initializes the windows verficiation bits and other things we're better off
 * doing after main() has passed on it's data.
 *
 * @param   fFlags          The main flags.
 * @param   fAvastKludge    Whether to apply the avast kludge.
 */
DECLHIDDEN(void) supR3HardenedWinInit(uint32_t fFlags, bool fAvastKludge)
{
    NTSTATUS rcNt;

#ifndef VBOX_WITHOUT_DEBUGGER_CHECKS
    /*
     * Install a anti debugging hack before we continue.  This prevents most
     * notifications from ending up in the debugger. (Also applied to the
     * child process when respawning.)
     */
    rcNt = NtSetInformationThread(NtCurrentThread(), ThreadHideFromDebugger, NULL, 0);
    if (!NT_SUCCESS(rcNt))
        supR3HardenedFatalMsg("supR3HardenedWinInit", kSupInitOp_Misc, VERR_GENERAL_FAILURE,
                              "NtSetInformationThread/ThreadHideFromDebugger failed: %#x\n", rcNt);
#endif

    /*
     * Init the verifier.
     */
    RTErrInfoInitStatic(&g_ErrInfoStatic);
    int rc = supHardenedWinInitImageVerifier(&g_ErrInfoStatic.Core);
    if (RT_FAILURE(rc))
        supR3HardenedFatalMsg("supR3HardenedWinInit", kSupInitOp_Misc, rc,
                              "supHardenedWinInitImageVerifier failed: %s", g_ErrInfoStatic.szMsg);

    /*
     * Get the windows system directory from the KnownDlls dir.
     */
    HANDLE              hSymlink = INVALID_HANDLE_VALUE;
    UNICODE_STRING      UniStr = RTNT_CONSTANT_UNISTR(L"\\KnownDlls\\KnownDllPath");
    OBJECT_ATTRIBUTES   ObjAttrs;
    InitializeObjectAttributes(&ObjAttrs, &UniStr, OBJ_CASE_INSENSITIVE, NULL /*hRootDir*/, NULL /*pSecDesc*/);
    rcNt = NtOpenSymbolicLinkObject(&hSymlink, SYMBOLIC_LINK_QUERY, &ObjAttrs);
    if (!NT_SUCCESS(rcNt))
        supR3HardenedFatalMsg("supR3HardenedWinInit", kSupInitOp_Misc, rcNt, "Error opening '%ls': %#x", UniStr.Buffer, rcNt);

    g_System32WinPath.UniStr.Buffer = g_System32WinPath.awcBuffer;
    g_System32WinPath.UniStr.Length = 0;
    g_System32WinPath.UniStr.MaximumLength = sizeof(g_System32WinPath.awcBuffer) - sizeof(RTUTF16);
    rcNt = NtQuerySymbolicLinkObject(hSymlink, &g_System32WinPath.UniStr, NULL);
    if (!NT_SUCCESS(rcNt))
        supR3HardenedFatalMsg("supR3HardenedWinInit", kSupInitOp_Misc, rcNt, "Error querying '%ls': %#x", UniStr.Buffer, rcNt);
    g_System32WinPath.UniStr.Buffer[g_System32WinPath.UniStr.Length / sizeof(RTUTF16)] = '\0';

    SUP_DPRINTF(("KnownDllPath: %ls\n", g_System32WinPath.UniStr.Buffer));
    NtClose(hSymlink);

    if (!(fFlags & SUPSECMAIN_FLAGS_DONT_OPEN_DEV))
    {
        if (fAvastKludge)
        {
            /*
             * Do a self purification to cure avast's weird NtOpenFile write-thru
             * change in GetBinaryTypeW change in kernel32.  Unfortunately, avast
             * uses a system thread to perform the process modifications, which
             * means it's hard to make sure it had the chance to make them...
             *
             * We have to resort to kludge doing yield and sleep fudging for a
             * number of milliseconds and schedulings before we can hope that avast
             * and similar products have done what they need to do.  If we do any
             * fixes, we wait for a while again and redo it until we're clean.
             *
             * This is unfortunately kind of fragile.
             */
            uint32_t cMsFudge = g_fSupAdversaries ? 512 : 128;
            uint32_t cFixes;
            for (uint32_t iLoop = 0; iLoop < 16; iLoop++)
            {
                uint32_t cSleeps = 0;
                uint64_t uMsTsStart = supR3HardenedWinGetMilliTS();
                do
                {
                    NtYieldExecution();
                    LARGE_INTEGER Time;
                    Time.QuadPart = -8000000 / 100; /* 8ms in 100ns units, relative time. */
                    NtDelayExecution(FALSE, &Time);
                    cSleeps++;
                } while (   supR3HardenedWinGetMilliTS() - uMsTsStart <= cMsFudge
                         || cSleeps < 8);
                SUP_DPRINTF(("supR3HardenedWinInit: Startup delay kludge #2/%u: %u ms, %u sleeps\n",
                             iLoop, supR3HardenedWinGetMilliTS() - uMsTsStart, cSleeps));

                cFixes = 0;
                rc = supHardenedWinVerifyProcess(NtCurrentProcess(), NtCurrentThread(), SUPHARDNTVPKIND_SELF_PURIFICATION,
                                                 0 /*fFlags*/, &cFixes, NULL /*pErrInfo*/);
                if (RT_FAILURE(rc) || cFixes == 0)
                    break;

                if (!g_fSupAdversaries)
                    g_fSupAdversaries |= SUPHARDNT_ADVERSARY_UNKNOWN;
                cMsFudge = 512;

                /* Log the KiOpPrefetchPatchCount value if available, hoping it might sched some light on spider38's case. */
                ULONG cPatchCount = 0;
                rcNt = NtQuerySystemInformation(SystemInformation_KiOpPrefetchPatchCount,
                                                &cPatchCount, sizeof(cPatchCount), NULL);
                if (NT_SUCCESS(rcNt))
                    SUP_DPRINTF(("supR3HardenedWinInit: cFixes=%u g_fSupAdversaries=%#x cPatchCount=%#u\n",
                                 cFixes, g_fSupAdversaries, cPatchCount));
                else
                    SUP_DPRINTF(("supR3HardenedWinInit: cFixes=%u g_fSupAdversaries=%#x\n", cFixes, g_fSupAdversaries));
            }
        }

        /*
         * Install the hooks.
         */
        supR3HardenedWinInstallHooks();
    }
    else if (fFlags & SUPSECMAIN_FLAGS_FIRST_PROCESS)
    {
        /*
         * Try shake anyone (e.g. easyhook) patching process creation code in
         * kernelbase, kernel32 or ntdll so they won't so easily cause the child
         * to crash when we respawn and purify it.
         */
        SUP_DPRINTF(("supR3HardenedWinInit: Performing a limited self purification...\n"));
        uint32_t cFixes = 0;
        rc = supHardenedWinVerifyProcess(NtCurrentProcess(), NtCurrentThread(), SUPHARDNTVPKIND_SELF_PURIFICATION_LIMITED,
                                         0 /*fFlags*/, &cFixes, NULL /*pErrInfo*/);
        SUP_DPRINTF(("supR3HardenedWinInit: SUPHARDNTVPKIND_SELF_PURIFICATION_LIMITED -> %Rrc, cFixes=%d\n", rc, cFixes));
        RT_NOREF(rc); /* ignored on purpose */
    }

#ifndef VBOX_WITH_VISTA_NO_SP
    /*
     * Complain about Vista w/o service pack if we're launching a VM.
     */
    if (   !(fFlags & SUPSECMAIN_FLAGS_DONT_OPEN_DEV)
        && g_uNtVerCombined >= SUP_NT_VER_VISTA
        && g_uNtVerCombined <  SUP_MAKE_NT_VER_COMBINED(6, 0, 6001, 0, 0))
        supR3HardenedFatalMsg("supR3HardenedWinInit", kSupInitOp_Misc, VERR_NOT_SUPPORTED,
                              "Window Vista without any service pack installed is not supported. Please install the latest service pack.");
#endif
}


/**
 * Modifies the DLL search path for testcases.
 *
 * This makes sure the application binary path is in the search path.  When
 * starting a testcase executable in the testcase/ subdirectory this isn't the
 * case by default.  So, unless we do something about it we won't be able to
 * import VBox DLLs.
 *
 * @param   fFlags          The main flags (giving the location).
 * @param   pszAppBinPath   The path to the application binary directory
 *                          (windows style).
 */
DECLHIDDEN(void) supR3HardenedWinModifyDllSearchPath(uint32_t fFlags, const char *pszAppBinPath)
{
    /*
     * For the testcases to work, we must add the app bin directory to the
     * DLL search list before the testcase dll is loaded or it won't be
     * able to find the VBox DLLs.  This is done _after_ VBoxRT.dll is
     * initialized and sets its defaults.
     */
    switch (fFlags & SUPSECMAIN_FLAGS_LOC_MASK)
    {
        case SUPSECMAIN_FLAGS_LOC_TESTCASE:
            break;
        default:
            return;
    }

    /*
     * Dynamically resolve the two APIs we need (the latter uses forwarders on w7).
     */
    HMODULE hModKernel32 = GetModuleHandleW(L"kernel32.dll");

    typedef BOOL (WINAPI *PFNSETDLLDIRECTORY)(LPCWSTR);
    PFNSETDLLDIRECTORY pfnSetDllDir;
    pfnSetDllDir     = (PFNSETDLLDIRECTORY)GetProcAddress(hModKernel32, "SetDllDirectoryW");

    typedef BOOL (WINAPI *PFNSETDEFAULTDLLDIRECTORIES)(DWORD);
    PFNSETDEFAULTDLLDIRECTORIES pfnSetDefDllDirs;
    pfnSetDefDllDirs = (PFNSETDEFAULTDLLDIRECTORIES)GetProcAddress(hModKernel32, "SetDefaultDllDirectories");

    if (pfnSetDllDir != NULL)
    {
        /*
         * Convert the path to UTF-16 and try set it.
         */
        PRTUTF16 pwszAppBinPath = NULL;
        int rc = RTStrToUtf16(pszAppBinPath, &pwszAppBinPath);
        if (RT_SUCCESS(rc))
        {
            if (pfnSetDllDir(pwszAppBinPath))
            {
                SUP_DPRINTF(("supR3HardenedWinModifyDllSearchPath: Set dll dir to '%ls'\n", pwszAppBinPath));
                g_fSupLibHardenedDllSearchUserDirs = true;

                /*
                 * We set it alright, on W7 and later we also must modify the
                 * default DLL search order.  See @bugref{6861} for details on
                 * why we don't do this on Vista (also see init-win.cpp in IPRT).
                 */
                if (   pfnSetDefDllDirs
                    && g_uNtVerCombined >= SUP_NT_VER_W70)
                {
                    if (pfnSetDefDllDirs(  LOAD_LIBRARY_SEARCH_APPLICATION_DIR
                                         | LOAD_LIBRARY_SEARCH_SYSTEM32
                                         | LOAD_LIBRARY_SEARCH_USER_DIRS))
                        SUP_DPRINTF(("supR3HardenedWinModifyDllSearchPath: Successfully modified search dirs.\n"));
                    else
                        supR3HardenedFatal("supR3HardenedWinModifyDllSearchPath: SetDllDirectoryW(%ls) failed: %d\n",
                                           pwszAppBinPath, RtlGetLastWin32Error());
                }
            }
            else
                supR3HardenedFatal("supR3HardenedWinModifyDllSearchPath: SetDllDirectoryW(%ls) failed: %d\n",
                                   pwszAppBinPath, RtlGetLastWin32Error());
            RTUtf16Free(pwszAppBinPath);
        }
        else
            supR3HardenedFatal("supR3HardenedWinModifyDllSearchPath: RTStrToUtf16(%s) failed: %d\n", pszAppBinPath, rc);
    }
}


/**
 * Initializes the application binary directory path.
 *
 * This is called once or twice.
 *
 * @param   fFlags          The main flags (giving the location).
 */
DECLHIDDEN(void) supR3HardenedWinInitAppBin(uint32_t fFlags)
{
    USHORT cwc = (USHORT)g_offSupLibHardenedExeNtName - 1;
    g_SupLibHardenedAppBinNtPath.UniStr.Buffer = g_SupLibHardenedAppBinNtPath.awcBuffer;
    memcpy(g_SupLibHardenedAppBinNtPath.UniStr.Buffer, g_SupLibHardenedExeNtPath.UniStr.Buffer, cwc * sizeof(WCHAR));

    switch (fFlags & SUPSECMAIN_FLAGS_LOC_MASK)
    {
        case SUPSECMAIN_FLAGS_LOC_APP_BIN:
            break;
        case SUPSECMAIN_FLAGS_LOC_TESTCASE:
        {
            /* Drop one directory level. */
            USHORT off = cwc;
            WCHAR  wc;
            while (   off > 1
                   && (wc = g_SupLibHardenedAppBinNtPath.UniStr.Buffer[off - 1]) != '\0')
                if (wc != '\\' && wc != '/')
                    off--;
                else
                {
                    if (g_SupLibHardenedAppBinNtPath.UniStr.Buffer[off - 2] == ':')
                        cwc = off;
                    else
                        cwc = off - 1;
                    break;
                }
            break;
        }
        default:
            supR3HardenedFatal("supR3HardenedWinInitAppBin: Unknown program binary location: %#x\n", fFlags);
    }

    g_SupLibHardenedAppBinNtPath.UniStr.Buffer[cwc]   = '\0';
    g_SupLibHardenedAppBinNtPath.UniStr.Length        = cwc * sizeof(WCHAR);
    g_SupLibHardenedAppBinNtPath.UniStr.MaximumLength = sizeof(g_SupLibHardenedAppBinNtPath.awcBuffer);
    SUP_DPRINTF(("supR3HardenedWinInitAppBin(%#x): '%ls'\n", fFlags, g_SupLibHardenedAppBinNtPath.UniStr.Buffer));
}


/**
 * Converts the Windows command line string (UTF-16) to an array of UTF-8
 * arguments suitable for passing to main().
 *
 * @returns Pointer to the argument array.
 * @param   pawcCmdLine         The UTF-16 windows command line to parse.
 * @param   cwcCmdLine          The length of the command line.
 * @param   pcArgs              Where to return the number of arguments.
 */
static char **suplibCommandLineToArgvWStub(PCRTUTF16 pawcCmdLine, size_t cwcCmdLine, int *pcArgs)
{
    /*
     * Convert the command line string to UTF-8.
     */
    char *pszCmdLine = NULL;
    SUPR3HARDENED_ASSERT(RT_SUCCESS(RTUtf16ToUtf8Ex(pawcCmdLine, cwcCmdLine, &pszCmdLine, 0, NULL)));

    /*
     * Parse the command line, carving argument strings out of it.
     */
    int    cArgs          = 0;
    int    cArgsAllocated = 4;
    char **papszArgs      = (char **)RTMemAllocZ(sizeof(char *) * cArgsAllocated);
    char  *pszSrc         = pszCmdLine;
    for (;;)
    {
        /* skip leading blanks. */
        char ch = *pszSrc;
        while (suplibCommandLineIsArgSeparator(ch))
            ch = *++pszSrc;
        if (!ch)
            break;

        /* Add argument to the vector. */
        if (cArgs + 2 >= cArgsAllocated)
        {
            cArgsAllocated *= 2;
            papszArgs = (char **)RTMemRealloc(papszArgs, sizeof(char *) * cArgsAllocated);
        }
        papszArgs[cArgs++] = pszSrc;
        papszArgs[cArgs]   = NULL;

        /* Unquote and unescape the string. */
        char *pszDst = pszSrc++;
        bool fQuoted = false;
        do
        {
            if (ch == '"')
                fQuoted = !fQuoted;
            else if (ch != '\\' || (*pszSrc != '\\' && *pszSrc != '"'))
                *pszDst++ = ch;
            else
            {
                unsigned cSlashes = 0;
                while ((ch = *pszSrc++) == '\\')
                    cSlashes++;
                if (ch == '"')
                {
                    while (cSlashes >= 2)
                    {
                        cSlashes -= 2;
                        *pszDst++ = '\\';
                    }
                    if (cSlashes)
                        *pszDst++ = '"';
                    else
                        fQuoted = !fQuoted;
                }
                else
                {
                    pszSrc--;
                    while (cSlashes-- > 0)
                        *pszDst++ = '\\';
                }
            }

            ch = *pszSrc++;
        } while (ch != '\0' && (fQuoted || !suplibCommandLineIsArgSeparator(ch)));

        /* Terminate the argument. */
        *pszDst = '\0';
        if (!ch)
            break;
    }

    *pcArgs = cArgs;
    return papszArgs;
}


/**
 * Worker for supR3HardenedFindVersionRsrcOffset.
 *
 * @returns RVA the version resource data, UINT32_MAX if not found.
 * @param   pRootDir            The root resource directory.  Expects data to
 *                              follow.
 * @param   cbBuf               The amount of data at pRootDir.
 * @param   offData             The offset to the data entry.
 * @param   pcbData             Where to return the size of the data.
 */
static uint32_t supR3HardenedGetRvaFromRsrcDataEntry(PIMAGE_RESOURCE_DIRECTORY pRootDir, uint32_t cbBuf, uint32_t offData,
                                                     uint32_t *pcbData)
{
    if (   offData <= cbBuf
        && offData + sizeof(IMAGE_RESOURCE_DATA_ENTRY) <= cbBuf)
    {
        PIMAGE_RESOURCE_DATA_ENTRY pRsrcData = (PIMAGE_RESOURCE_DATA_ENTRY)((uintptr_t)pRootDir + offData);
        SUP_DPRINTF(("    [Raw version resource data: %#x LB %#x, codepage %#x (reserved %#x)]\n",
                     pRsrcData->OffsetToData, pRsrcData->Size, pRsrcData->CodePage, pRsrcData->Reserved));
        if (pRsrcData->Size > 0)
        {
            *pcbData = pRsrcData->Size;
            return pRsrcData->OffsetToData;
        }
    }
    else
        SUP_DPRINTF(("    Version resource data (%#x) is outside the buffer (%#x)! :-(\n", offData, cbBuf));

    *pcbData = 0;
    return UINT32_MAX;
}


/** @def SUP_RSRC_DPRINTF
 * Dedicated debug printf for resource directory parsing.
 * @sa SUP_DPRINTF
 */
#if 0 /* more details */
# define SUP_RSRC_DPRINTF(a) SUP_DPRINTF(a)
#else
# define SUP_RSRC_DPRINTF(a) do { } while (0)
#endif

/**
 * Scans the resource directory for a version resource.
 *
 * @returns RVA of the version resource data, UINT32_MAX if not found.
 * @param   pRootDir            The root resource directory.  Expects data to
 *                              follow.
 * @param   cbBuf               The amount of data at pRootDir.
 * @param   pcbData             Where to return the size of the version data.
 */
static uint32_t supR3HardenedFindVersionRsrcRva(PIMAGE_RESOURCE_DIRECTORY pRootDir, uint32_t cbBuf, uint32_t *pcbData)
{
    SUP_RSRC_DPRINTF(("    ResDir: Char=%#x Time=%#x Ver=%d%d #NamedEntries=%#x #IdEntries=%#x\n",
                      pRootDir->Characteristics,
                      pRootDir->TimeDateStamp,
                      pRootDir->MajorVersion,
                      pRootDir->MinorVersion,
                      pRootDir->NumberOfNamedEntries,
                      pRootDir->NumberOfIdEntries));

    PIMAGE_RESOURCE_DIRECTORY_ENTRY paEntries = (PIMAGE_RESOURCE_DIRECTORY_ENTRY)(pRootDir + 1);
    unsigned cMaxEntries = (cbBuf - sizeof(IMAGE_RESOURCE_DIRECTORY)) / sizeof(IMAGE_RESOURCE_DIRECTORY_ENTRY);
    unsigned cEntries    = pRootDir->NumberOfNamedEntries + pRootDir->NumberOfIdEntries;
    if (cEntries > cMaxEntries)
        cEntries = cMaxEntries;
    for (unsigned i = 0; i < cEntries; i++)
    {
        if (!paEntries[i].NameIsString)
        {
            if (!paEntries[i].DataIsDirectory)
                SUP_RSRC_DPRINTF(("    #%u:   ID: #%#06x  Data: %#010x\n",
                                i, paEntries[i].Id, paEntries[i].OffsetToData));
            else
                SUP_RSRC_DPRINTF(("    #%u:   ID: #%#06x  Dir: %#010x\n",
                                i, paEntries[i].Id, paEntries[i].OffsetToDirectory));
        }
        else
        {
            if (!paEntries[i].DataIsDirectory)
                SUP_RSRC_DPRINTF(("    #%u: Name: #%#06x  Data: %#010x\n",
                                i, paEntries[i].NameOffset, paEntries[i].OffsetToData));
            else
                SUP_RSRC_DPRINTF(("    #%u: Name: #%#06x  Dir: %#010x\n",
                                i, paEntries[i].NameOffset, paEntries[i].OffsetToDirectory));
        }

        /*
         * Look for the version resource type.  Skip to the next entry if not found.
         */
        if (paEntries[i].NameIsString)
            continue;
        if (paEntries[i].Id != 0x10 /*RT_VERSION*/)
            continue;
        if (!paEntries[i].DataIsDirectory)
        {
            SUP_DPRINTF(("    #%u:   ID: #%#06x  Data: %#010x - WEIRD!\n", i, paEntries[i].Id, paEntries[i].OffsetToData));
            continue;
        }
        SUP_RSRC_DPRINTF(("    Version resource dir entry #%u: dir offset: %#x (cbBuf=%#x)\n",
                          i, paEntries[i].OffsetToDirectory, cbBuf));

        /*
         * Locate the sub-resource directory for it.
         */
        if (paEntries[i].OffsetToDirectory >= cbBuf)
        {
            SUP_DPRINTF(("    Version resource dir is outside the buffer! :-(\n"));
            continue;
        }
        uint32_t cbMax = cbBuf - paEntries[i].OffsetToDirectory;
        if (cbMax < sizeof(IMAGE_RESOURCE_DIRECTORY) + sizeof(IMAGE_RESOURCE_DIRECTORY_ENTRY))
        {
            SUP_DPRINTF(("    Version resource dir entry #0 is outside the buffer! :-(\n"));
            continue;
        }
        PIMAGE_RESOURCE_DIRECTORY pVerDir = (PIMAGE_RESOURCE_DIRECTORY)((uintptr_t)pRootDir + paEntries[i].OffsetToDirectory);
        SUP_RSRC_DPRINTF(("    VerDir: Char=%#x Time=%#x Ver=%d%d #NamedEntries=%#x #IdEntries=%#x\n",
                          pVerDir->Characteristics,
                          pVerDir->TimeDateStamp,
                          pVerDir->MajorVersion,
                          pVerDir->MinorVersion,
                          pVerDir->NumberOfNamedEntries,
                          pVerDir->NumberOfIdEntries));
        PIMAGE_RESOURCE_DIRECTORY_ENTRY paVerEntries = (PIMAGE_RESOURCE_DIRECTORY_ENTRY)(pVerDir + 1);
        unsigned cMaxVerEntries = (cbMax - sizeof(IMAGE_RESOURCE_DIRECTORY)) / sizeof(IMAGE_RESOURCE_DIRECTORY_ENTRY);
        unsigned cVerEntries    = pVerDir->NumberOfNamedEntries + pVerDir->NumberOfIdEntries;
        if (cVerEntries > cMaxVerEntries)
            cVerEntries = cMaxVerEntries;
        for (unsigned iVer = 0; iVer < cVerEntries; iVer++)
        {
            if (!paVerEntries[iVer].NameIsString)
            {
                if (!paVerEntries[iVer].DataIsDirectory)
                    SUP_RSRC_DPRINTF(("      #%u:   ID: #%#06x  Data: %#010x\n",
                                      iVer, paVerEntries[iVer].Id, paVerEntries[iVer].OffsetToData));
                else
                    SUP_RSRC_DPRINTF(("      #%u:   ID: #%#06x  Dir: %#010x\n",
                                      iVer, paVerEntries[iVer].Id, paVerEntries[iVer].OffsetToDirectory));
            }
            else
            {
                if (!paVerEntries[iVer].DataIsDirectory)
                    SUP_RSRC_DPRINTF(("      #%u: Name: #%#06x  Data: %#010x\n",
                                      iVer, paVerEntries[iVer].NameOffset, paVerEntries[iVer].OffsetToData));
                else
                    SUP_RSRC_DPRINTF(("      #%u: Name: #%#06x  Dir: %#010x\n",
                                      iVer, paVerEntries[iVer].NameOffset, paVerEntries[iVer].OffsetToDirectory));
            }
            if (!paVerEntries[iVer].DataIsDirectory)
            {
                SUP_DPRINTF(("    [Version info resource found at %#x! (ID/Name: #%#x)]\n",
                             paVerEntries[iVer].OffsetToData, paVerEntries[iVer].Name));
                return supR3HardenedGetRvaFromRsrcDataEntry(pRootDir, cbBuf, paVerEntries[iVer].OffsetToData, pcbData);
            }

            /*
             * Check out the next directory level.
             */
            if (paVerEntries[iVer].OffsetToDirectory >= cbBuf)
            {
                SUP_DPRINTF(("    Version resource subdir is outside the buffer! :-(\n"));
                continue;
            }
            cbMax = cbBuf - paVerEntries[iVer].OffsetToDirectory;
            if (cbMax < sizeof(IMAGE_RESOURCE_DIRECTORY) + sizeof(IMAGE_RESOURCE_DIRECTORY_ENTRY))
            {
                SUP_DPRINTF(("    Version resource subdir entry #0 is outside the buffer! :-(\n"));
                continue;
            }
            PIMAGE_RESOURCE_DIRECTORY pVerSubDir = (PIMAGE_RESOURCE_DIRECTORY)((uintptr_t)pRootDir + paVerEntries[iVer].OffsetToDirectory);
            SUP_RSRC_DPRINTF(("      VerSubDir#%u: Char=%#x Time=%#x Ver=%d%d #NamedEntries=%#x #IdEntries=%#x\n",
                              iVer,
                              pVerSubDir->Characteristics,
                              pVerSubDir->TimeDateStamp,
                              pVerSubDir->MajorVersion,
                              pVerSubDir->MinorVersion,
                              pVerSubDir->NumberOfNamedEntries,
                              pVerSubDir->NumberOfIdEntries));
            PIMAGE_RESOURCE_DIRECTORY_ENTRY paVerSubEntries = (PIMAGE_RESOURCE_DIRECTORY_ENTRY)(pVerSubDir + 1);
            unsigned cMaxVerSubEntries = (cbMax - sizeof(IMAGE_RESOURCE_DIRECTORY)) / sizeof(IMAGE_RESOURCE_DIRECTORY_ENTRY);
            unsigned cVerSubEntries    = pVerSubDir->NumberOfNamedEntries + pVerSubDir->NumberOfIdEntries;
            if (cVerSubEntries > cMaxVerSubEntries)
                cVerSubEntries = cMaxVerSubEntries;
            for (unsigned iVerSub = 0; iVerSub < cVerSubEntries; iVerSub++)
            {
                if (!paVerSubEntries[iVerSub].NameIsString)
                {
                    if (!paVerSubEntries[iVerSub].DataIsDirectory)
                        SUP_RSRC_DPRINTF(("        #%u:   ID: #%#06x  Data: %#010x\n",
                                          iVerSub, paVerSubEntries[iVerSub].Id, paVerSubEntries[iVerSub].OffsetToData));
                    else
                        SUP_RSRC_DPRINTF(("        #%u:   ID: #%#06x  Dir: %#010x\n",
                                          iVerSub, paVerSubEntries[iVerSub].Id, paVerSubEntries[iVerSub].OffsetToDirectory));
                }
                else
                {
                    if (!paVerSubEntries[iVerSub].DataIsDirectory)
                        SUP_RSRC_DPRINTF(("        #%u: Name: #%#06x  Data: %#010x\n",
                                          iVerSub, paVerSubEntries[iVerSub].NameOffset, paVerSubEntries[iVerSub].OffsetToData));
                    else
                        SUP_RSRC_DPRINTF(("        #%u: Name: #%#06x  Dir: %#010x\n",
                                          iVerSub, paVerSubEntries[iVerSub].NameOffset, paVerSubEntries[iVerSub].OffsetToDirectory));
                }
                if (!paVerSubEntries[iVerSub].DataIsDirectory)
                {
                    SUP_DPRINTF(("    [Version info resource found at %#x! (ID/Name: %#x; SubID/SubName: %#x)]\n",
                                 paVerSubEntries[iVerSub].OffsetToData, paVerEntries[iVer].Name, paVerSubEntries[iVerSub].Name));
                    return supR3HardenedGetRvaFromRsrcDataEntry(pRootDir, cbBuf, paVerSubEntries[iVerSub].OffsetToData, pcbData);
                }
            }
        }
    }

    *pcbData = 0;
    return UINT32_MAX;
}


/**
 * Logs information about a file from a protection product or from Windows,
 * optionally returning the file version.
 *
 * The purpose here is to better see which version of the product is installed
 * and not needing to depend on the user supplying the correct information.
 *
 * @param   pwszFile        The NT path to the file.
 * @param   pwszFileVersion Where to return the file version, if found. NULL if
 *                          not interested.
 * @param   cwcFileVersion  The size of the file version buffer (UTF-16 units).
 */
static void supR3HardenedLogFileInfo(PCRTUTF16 pwszFile, PRTUTF16 pwszFileVersion, size_t cwcFileVersion)
{
    /*
     * Make sure the file version is always set when we return.
     */
    if (pwszFileVersion && cwcFileVersion)
        *pwszFileVersion = '\0';

    /*
     * Open the file.
     */
    HANDLE              hFile  = RTNT_INVALID_HANDLE_VALUE;
    IO_STATUS_BLOCK     Ios    = RTNT_IO_STATUS_BLOCK_INITIALIZER;
    UNICODE_STRING      UniStrName;
    UniStrName.Buffer = (WCHAR *)pwszFile;
    UniStrName.Length = (USHORT)(RTUtf16Len(pwszFile) * sizeof(WCHAR));
    UniStrName.MaximumLength = UniStrName.Length + sizeof(WCHAR);
    OBJECT_ATTRIBUTES   ObjAttr;
    InitializeObjectAttributes(&ObjAttr, &UniStrName, OBJ_CASE_INSENSITIVE, NULL /*hRootDir*/, NULL /*pSecDesc*/);
    NTSTATUS rcNt = NtCreateFile(&hFile,
                                 GENERIC_READ | SYNCHRONIZE,
                                 &ObjAttr,
                                 &Ios,
                                 NULL /* Allocation Size*/,
                                 FILE_ATTRIBUTE_NORMAL,
                                 FILE_SHARE_READ,
                                 FILE_OPEN,
                                 FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT,
                                 NULL /*EaBuffer*/,
                                 0 /*EaLength*/);
    if (NT_SUCCESS(rcNt))
        rcNt = Ios.Status;
    if (NT_SUCCESS(rcNt))
    {
        SUP_DPRINTF(("%ls:\n", pwszFile));
        union
        {
            uint64_t                    u64AlignmentInsurance;
            FILE_BASIC_INFORMATION      BasicInfo;
            FILE_STANDARD_INFORMATION   StdInfo;
            uint8_t                     abBuf[32768];
            RTUTF16                     awcBuf[16384];
            IMAGE_DOS_HEADER            MzHdr;
            IMAGE_RESOURCE_DIRECTORY    ResDir;
        } u;
        RTTIMESPEC  TimeSpec;
        char        szTmp[64];

        /*
         * Print basic file information available via NtQueryInformationFile.
         */
        RTNT_IO_STATUS_BLOCK_REINIT(&Ios);
        rcNt = NtQueryInformationFile(hFile, &Ios, &u.BasicInfo, sizeof(u.BasicInfo), FileBasicInformation);
        if (NT_SUCCESS(rcNt) && NT_SUCCESS(Ios.Status))
        {
            SUP_DPRINTF(("    CreationTime:    %s\n", RTTimeSpecToString(RTTimeSpecSetNtTime(&TimeSpec, u.BasicInfo.CreationTime.QuadPart), szTmp, sizeof(szTmp))));
            /*SUP_DPRINTF(("    LastAccessTime:  %s\n", RTTimeSpecToString(RTTimeSpecSetNtTime(&TimeSpec, u.BasicInfo.LastAccessTime.QuadPart), szTmp, sizeof(szTmp))));*/
            SUP_DPRINTF(("    LastWriteTime:   %s\n", RTTimeSpecToString(RTTimeSpecSetNtTime(&TimeSpec, u.BasicInfo.LastWriteTime.QuadPart), szTmp, sizeof(szTmp))));
            SUP_DPRINTF(("    ChangeTime:      %s\n", RTTimeSpecToString(RTTimeSpecSetNtTime(&TimeSpec, u.BasicInfo.ChangeTime.QuadPart), szTmp, sizeof(szTmp))));
            SUP_DPRINTF(("    FileAttributes:  %#x\n", u.BasicInfo.FileAttributes));
        }
        else
            SUP_DPRINTF(("    FileBasicInformation -> %#x %#x\n", rcNt, Ios.Status));

        RTNT_IO_STATUS_BLOCK_REINIT(&Ios);
        rcNt = NtQueryInformationFile(hFile, &Ios, &u.StdInfo, sizeof(u.StdInfo), FileStandardInformation);
        if (NT_SUCCESS(rcNt) && NT_SUCCESS(Ios.Status))
            SUP_DPRINTF(("    Size:            %#llx\n", u.StdInfo.EndOfFile.QuadPart));
        else
            SUP_DPRINTF(("    FileStandardInformation -> %#x %#x\n", rcNt, Ios.Status));

        /*
         * Read the image header and extract the timestamp and other useful info.
         */
        RT_ZERO(u);
        RTNT_IO_STATUS_BLOCK_REINIT(&Ios);
        LARGE_INTEGER offRead;
        offRead.QuadPart = 0;
        rcNt = NtReadFile(hFile, NULL /*hEvent*/, NULL /*ApcRoutine*/, NULL /*ApcContext*/, &Ios,
                          &u, (ULONG)sizeof(u), &offRead, NULL);
        if (NT_SUCCESS(rcNt) && NT_SUCCESS(Ios.Status))
        {
            uint32_t offNtHdrs = 0;
            if (u.MzHdr.e_magic == IMAGE_DOS_SIGNATURE)
                offNtHdrs = u.MzHdr.e_lfanew;
            if (offNtHdrs < sizeof(u) - sizeof(IMAGE_NT_HEADERS))
            {
                PIMAGE_NT_HEADERS64 pNtHdrs64 = (PIMAGE_NT_HEADERS64)&u.abBuf[offNtHdrs];
                PIMAGE_NT_HEADERS32 pNtHdrs32 = (PIMAGE_NT_HEADERS32)&u.abBuf[offNtHdrs];
                if (pNtHdrs64->Signature == IMAGE_NT_SIGNATURE)
                {
                    SUP_DPRINTF(("    NT Headers:      %#x\n", offNtHdrs));
                    SUP_DPRINTF(("    Timestamp:       %#x\n", pNtHdrs64->FileHeader.TimeDateStamp));
                    SUP_DPRINTF(("    Machine:         %#x%s\n", pNtHdrs64->FileHeader.Machine,
                                 pNtHdrs64->FileHeader.Machine == IMAGE_FILE_MACHINE_I386 ? " - i386"
                                 : pNtHdrs64->FileHeader.Machine == IMAGE_FILE_MACHINE_AMD64 ? " - amd64" : ""));
                    SUP_DPRINTF(("    Timestamp:       %#x\n", pNtHdrs64->FileHeader.TimeDateStamp));
                    SUP_DPRINTF(("    Image Version:   %u.%u\n",
                                 pNtHdrs64->OptionalHeader.MajorImageVersion, pNtHdrs64->OptionalHeader.MinorImageVersion));
                    SUP_DPRINTF(("    SizeOfImage:     %#x (%u)\n", pNtHdrs64->OptionalHeader.SizeOfImage, pNtHdrs64->OptionalHeader.SizeOfImage));

                    /*
                     * Very crude way to extract info from the file version resource.
                     */
                    PIMAGE_SECTION_HEADER paSectHdrs = (PIMAGE_SECTION_HEADER)(  (uintptr_t)&pNtHdrs64->OptionalHeader
                                                                               + pNtHdrs64->FileHeader.SizeOfOptionalHeader);
                    IMAGE_DATA_DIRECTORY  RsrcDir = { 0, 0 };
                    if (   pNtHdrs64->FileHeader.SizeOfOptionalHeader == sizeof(IMAGE_OPTIONAL_HEADER64)
                        && pNtHdrs64->OptionalHeader.NumberOfRvaAndSizes > IMAGE_DIRECTORY_ENTRY_RESOURCE)
                        RsrcDir = pNtHdrs64->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_RESOURCE];
                    else if (   pNtHdrs64->FileHeader.SizeOfOptionalHeader == sizeof(IMAGE_OPTIONAL_HEADER32)
                             && pNtHdrs32->OptionalHeader.NumberOfRvaAndSizes > IMAGE_DIRECTORY_ENTRY_RESOURCE)
                        RsrcDir = pNtHdrs32->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_RESOURCE];
                    SUP_DPRINTF(("    Resource Dir:    %#x LB %#x\n", RsrcDir.VirtualAddress, RsrcDir.Size));
                    if (   RsrcDir.VirtualAddress > offNtHdrs
                        && RsrcDir.Size > 0
                        &&    (uintptr_t)&u + sizeof(u) - (uintptr_t)paSectHdrs
                           >= pNtHdrs64->FileHeader.NumberOfSections * sizeof(IMAGE_SECTION_HEADER) )
                    {
                        uint32_t uRvaRsrcSect = 0;
                        uint32_t cbRsrcSect   = 0;
                        uint32_t offRsrcSect  = 0;
                        offRead.QuadPart = 0;
                        for (uint32_t i = 0; i < pNtHdrs64->FileHeader.NumberOfSections; i++)
                        {
                            uRvaRsrcSect = paSectHdrs[i].VirtualAddress;
                            cbRsrcSect   = paSectHdrs[i].Misc.VirtualSize;
                            offRsrcSect  = paSectHdrs[i].PointerToRawData;
                            if (   RsrcDir.VirtualAddress - uRvaRsrcSect < cbRsrcSect
                                && offRsrcSect > offNtHdrs)
                            {
                                offRead.QuadPart = offRsrcSect + (RsrcDir.VirtualAddress - uRvaRsrcSect);
                                break;
                            }
                        }
                        if (offRead.QuadPart > 0)
                        {
                            RTNT_IO_STATUS_BLOCK_REINIT(&Ios);
                            RT_ZERO(u);
                            rcNt = NtReadFile(hFile, NULL /*hEvent*/, NULL /*ApcRoutine*/, NULL /*ApcContext*/, &Ios,
                                              &u, (ULONG)sizeof(u), &offRead, NULL);
                            PCRTUTF16 pwcVersionData = &u.awcBuf[0];
                            size_t    cbVersionData  = sizeof(u);

                            if (NT_SUCCESS(rcNt) && NT_SUCCESS(Ios.Status))
                            {
                                /* Make it less crude by try find the version resource data. */
                                uint32_t  cbVersion;
                                uint32_t  uRvaVersion = supR3HardenedFindVersionRsrcRva(&u.ResDir, sizeof(u), &cbVersion);
                                NOREF(uRvaVersion);
                                if (   uRvaVersion != UINT32_MAX
                                    && cbVersion < cbRsrcSect
                                    && uRvaVersion - uRvaRsrcSect <= cbRsrcSect - cbVersion)
                                {
                                    uint32_t const offVersion = uRvaVersion - uRvaRsrcSect;
                                    if (   offVersion < sizeof(u)
                                        && offVersion + cbVersion <= sizeof(u))
                                    {
                                        pwcVersionData = (PCRTUTF16)&u.abBuf[offVersion];
                                        cbVersionData  = cbVersion;
                                    }
                                    else
                                    {
                                        offRead.QuadPart = offVersion + offRsrcSect;
                                        RT_ZERO(u);
                                        rcNt = NtReadFile(hFile, NULL /*hEvent*/, NULL /*ApcRoutine*/, NULL /*ApcContext*/, &Ios,
                                                          &u, (ULONG)sizeof(u), &offRead, NULL);
                                        pwcVersionData = &u.awcBuf[0];
                                        cbVersionData  = RT_MIN(cbVersion, sizeof(u));
                                    }
                                }
                            }

                            if (NT_SUCCESS(rcNt) && NT_SUCCESS(Ios.Status))
                            {
                                static const struct { PCRTUTF16 pwsz; size_t cb; bool fRet; } s_abFields[] =
                                {
#define MY_WIDE_STR_TUPLE(a_sz, a_fRet) { L ## a_sz, sizeof(L ## a_sz) - sizeof(RTUTF16), a_fRet }
                                    MY_WIDE_STR_TUPLE("ProductName",        false),
                                    MY_WIDE_STR_TUPLE("ProductVersion",     false),
                                    MY_WIDE_STR_TUPLE("FileVersion",        true),
                                    MY_WIDE_STR_TUPLE("SpecialBuild",       false),
                                    MY_WIDE_STR_TUPLE("PrivateBuild",       false),
                                    MY_WIDE_STR_TUPLE("FileDescription",    false),
#undef MY_WIDE_STR_TUPLE
                                };
                                for (uint32_t i = 0; i < RT_ELEMENTS(s_abFields); i++)
                                {
                                    if (cbVersionData <= s_abFields[i].cb + 10)
                                        continue;
                                    size_t          cwcLeft = (cbVersionData - s_abFields[i].cb - 10) / sizeof(RTUTF16);
                                    PCRTUTF16       pwc     = pwcVersionData;
                                    RTUTF16 const   wcFirst = *s_abFields[i].pwsz;
                                    while (cwcLeft-- > 0)
                                    {
                                        if (   pwc[0] == 1 /* wType == text */
                                            && pwc[1] == wcFirst)
                                        {
                                            if (memcmp(pwc + 1, s_abFields[i].pwsz, s_abFields[i].cb + sizeof(RTUTF16)) == 0)
                                            {
                                                size_t cwcField = s_abFields[i].cb / sizeof(RTUTF16);
                                                pwc     += cwcField + 2;
                                                cwcLeft -= cwcField + 2;
                                                for (uint32_t iPadding = 0; iPadding < 3; iPadding++, pwc++, cwcLeft--)
                                                    if (*pwc)
                                                        break;
                                                int rc = RTUtf16ValidateEncodingEx(pwc, cwcLeft,
                                                                                   RTSTR_VALIDATE_ENCODING_ZERO_TERMINATED);
                                                if (RT_SUCCESS(rc))
                                                {
                                                    SUP_DPRINTF(("    %ls:%*s %ls",
                                                                 s_abFields[i].pwsz, cwcField < 15 ? 15 - cwcField : 0, "", pwc));
                                                    if (   s_abFields[i].fRet
                                                        && pwszFileVersion
                                                        && cwcFileVersion > 1)
                                                        RTUtf16Copy(pwszFileVersion, cwcFileVersion, pwc);
                                                }
                                                else
                                                    SUP_DPRINTF(("    %ls:%*s rc=%Rrc",
                                                                 s_abFields[i].pwsz, cwcField < 15 ? 15 - cwcField : 0, "", rc));

                                                break;
                                            }
                                        }
                                        pwc++;
                                    }
                                }
                            }
                            else
                                SUP_DPRINTF(("    NtReadFile @%#llx -> %#x %#x\n", offRead.QuadPart, rcNt, Ios.Status));
                        }
                        else
                            SUP_DPRINTF(("    Resource section not found.\n"));
                    }
                }
                else
                    SUP_DPRINTF(("    Nt Headers @%#x: Invalid signature\n", offNtHdrs));
            }
            else
                SUP_DPRINTF(("    Nt Headers @%#x: out side buffer\n", offNtHdrs));
        }
        else
            SUP_DPRINTF(("    NtReadFile @0 -> %#x %#x\n", rcNt, Ios.Status));
        NtClose(hFile);
    }
}


/**
 * Scans the Driver directory for drivers which may invade our processes.
 *
 * @returns Mask of SUPHARDNT_ADVERSARY_XXX flags.
 *
 * @remarks The enumeration of \\Driver normally requires administrator
 *          privileges.  So, the detection we're doing here isn't always gonna
 *          work just based on that.
 *
 * @todo    Find drivers in \\FileSystems as well, then we could detect VrNsdDrv
 *          from ViRobot APT Shield 2.0.
 */
static uint32_t supR3HardenedWinFindAdversaries(void)
{
    static const struct
    {
        uint32_t    fAdversary;
        const char *pszDriver;
    } s_aDrivers[] =
    {
        { SUPHARDNT_ADVERSARY_SYMANTEC_SYSPLANT,    "SysPlant" },

        { SUPHARDNT_ADVERSARY_SYMANTEC_N360,        "SRTSPX" },
        { SUPHARDNT_ADVERSARY_SYMANTEC_N360,        "SymDS" },
        { SUPHARDNT_ADVERSARY_SYMANTEC_N360,        "SymEvent" },
        { SUPHARDNT_ADVERSARY_SYMANTEC_N360,        "SymIRON" },
        { SUPHARDNT_ADVERSARY_SYMANTEC_N360,        "SymNetS" },

        { SUPHARDNT_ADVERSARY_AVAST,                "aswHwid" },
        { SUPHARDNT_ADVERSARY_AVAST,                "aswMonFlt" },
        { SUPHARDNT_ADVERSARY_AVAST,                "aswRdr2" },
        { SUPHARDNT_ADVERSARY_AVAST,                "aswRvrt" },
        { SUPHARDNT_ADVERSARY_AVAST,                "aswSnx" },
        { SUPHARDNT_ADVERSARY_AVAST,                "aswsp" },
        { SUPHARDNT_ADVERSARY_AVAST,                "aswStm" },
        { SUPHARDNT_ADVERSARY_AVAST,                "aswVmm" },

        { SUPHARDNT_ADVERSARY_TRENDMICRO,           "tmcomm" },
        { SUPHARDNT_ADVERSARY_TRENDMICRO,           "tmactmon" },
        { SUPHARDNT_ADVERSARY_TRENDMICRO,           "tmevtmgr" },
        { SUPHARDNT_ADVERSARY_TRENDMICRO,           "tmtdi" },
        { SUPHARDNT_ADVERSARY_TRENDMICRO,           "tmebc64" },  /* Titanium internet security, not officescan. */
        { SUPHARDNT_ADVERSARY_TRENDMICRO,           "tmeevw" },   /* Titanium internet security, not officescan. */
        { SUPHARDNT_ADVERSARY_TRENDMICRO,           "tmciesc" },  /* Titanium internet security, not officescan. */

        { SUPHARDNT_ADVERSARY_MCAFEE,               "cfwids" },
        { SUPHARDNT_ADVERSARY_MCAFEE,               "McPvDrv" },
        { SUPHARDNT_ADVERSARY_MCAFEE,               "mfeapfk" },
        { SUPHARDNT_ADVERSARY_MCAFEE,               "mfeavfk" },
        { SUPHARDNT_ADVERSARY_MCAFEE,               "mfefirek" },
        { SUPHARDNT_ADVERSARY_MCAFEE,               "mfehidk" },
        { SUPHARDNT_ADVERSARY_MCAFEE,               "mfencbdc" },
        { SUPHARDNT_ADVERSARY_MCAFEE,               "mfewfpk" },

        { SUPHARDNT_ADVERSARY_KASPERSKY,            "kl1" },
        { SUPHARDNT_ADVERSARY_KASPERSKY,            "klflt" },
        { SUPHARDNT_ADVERSARY_KASPERSKY,            "klif" },
        { SUPHARDNT_ADVERSARY_KASPERSKY,            "KLIM6" },
        { SUPHARDNT_ADVERSARY_KASPERSKY,            "klkbdflt" },
        { SUPHARDNT_ADVERSARY_KASPERSKY,            "klmouflt" },
        { SUPHARDNT_ADVERSARY_KASPERSKY,            "kltdi" },
        { SUPHARDNT_ADVERSARY_KASPERSKY,            "kneps" },

        { SUPHARDNT_ADVERSARY_MBAM,                 "MBAMWebAccessControl" },
        { SUPHARDNT_ADVERSARY_MBAM,                 "mbam" },
        { SUPHARDNT_ADVERSARY_MBAM,                 "mbamchameleon" },
        { SUPHARDNT_ADVERSARY_MBAM,                 "mwav" },
        { SUPHARDNT_ADVERSARY_MBAM,                 "mbamswissarmy" },

        { SUPHARDNT_ADVERSARY_AVG,                  "avgfwfd" },
        { SUPHARDNT_ADVERSARY_AVG,                  "avgtdia" },

        { SUPHARDNT_ADVERSARY_PANDA,                "PSINAflt" },
        { SUPHARDNT_ADVERSARY_PANDA,                "PSINFile" },
        { SUPHARDNT_ADVERSARY_PANDA,                "PSINKNC" },
        { SUPHARDNT_ADVERSARY_PANDA,                "PSINProc" },
        { SUPHARDNT_ADVERSARY_PANDA,                "PSINProt" },
        { SUPHARDNT_ADVERSARY_PANDA,                "PSINReg" },
        { SUPHARDNT_ADVERSARY_PANDA,                "PSKMAD" },
        { SUPHARDNT_ADVERSARY_PANDA,                "NNSAlpc" },
        { SUPHARDNT_ADVERSARY_PANDA,                "NNSHttp" },
        { SUPHARDNT_ADVERSARY_PANDA,                "NNShttps" },
        { SUPHARDNT_ADVERSARY_PANDA,                "NNSIds" },
        { SUPHARDNT_ADVERSARY_PANDA,                "NNSNAHSL" },
        { SUPHARDNT_ADVERSARY_PANDA,                "NNSpicc" },
        { SUPHARDNT_ADVERSARY_PANDA,                "NNSPihsw" },
        { SUPHARDNT_ADVERSARY_PANDA,                "NNSPop3" },
        { SUPHARDNT_ADVERSARY_PANDA,                "NNSProt" },
        { SUPHARDNT_ADVERSARY_PANDA,                "NNSPrv" },
        { SUPHARDNT_ADVERSARY_PANDA,                "NNSSmtp" },
        { SUPHARDNT_ADVERSARY_PANDA,                "NNSStrm" },
        { SUPHARDNT_ADVERSARY_PANDA,                "NNStlsc" },

        { SUPHARDNT_ADVERSARY_MSE,                  "NisDrv" },

        /*{ SUPHARDNT_ADVERSARY_COMODO, "cmdguard" }, file system */
        { SUPHARDNT_ADVERSARY_COMODO,               "inspect" },
        { SUPHARDNT_ADVERSARY_COMODO,               "cmdHlp" },

        { SUPHARDNT_ADVERSARY_DIGITAL_GUARDIAN_OLD, "dgmaster" },

        { SUPHARDNT_ADVERSARY_CYLANCE,              "cyprotectdrv" }, /* Not verified. */

        { SUPHARDNT_ADVERSARY_BEYONDTRUST,          "privman" },   /* Not verified. */
        { SUPHARDNT_ADVERSARY_BEYONDTRUST,          "privmanfi" }, /* Not verified. */

        { SUPHARDNT_ADVERSARY_AVECTO,               "PGDriver" },

        { SUPHARDNT_ADVERSARY_SOPHOS,               "SophosED" }, /* Not verified. */

        { SUPHARDNT_ADVERSARY_HORIZON_VIEW_AGENT,   "vmwicpdr" },
    };

    static const struct
    {
        uint32_t    fAdversary;
        PCRTUTF16   pwszFile;
    } s_aFiles[] =
    {
        { SUPHARDNT_ADVERSARY_SYMANTEC_SYSPLANT, L"\\SystemRoot\\System32\\drivers\\SysPlant.sys" },
        { SUPHARDNT_ADVERSARY_SYMANTEC_SYSPLANT, L"\\SystemRoot\\System32\\sysfer.dll" },
        { SUPHARDNT_ADVERSARY_SYMANTEC_SYSPLANT, L"\\SystemRoot\\System32\\sysferThunk.dll" },

        { SUPHARDNT_ADVERSARY_SYMANTEC_N360, L"\\SystemRoot\\System32\\drivers\\N360x64\\1505000.013\\ccsetx64.sys" },
        { SUPHARDNT_ADVERSARY_SYMANTEC_N360, L"\\SystemRoot\\System32\\drivers\\N360x64\\1505000.013\\ironx64.sys" },
        { SUPHARDNT_ADVERSARY_SYMANTEC_N360, L"\\SystemRoot\\System32\\drivers\\N360x64\\1505000.013\\srtsp64.sys" },
        { SUPHARDNT_ADVERSARY_SYMANTEC_N360, L"\\SystemRoot\\System32\\drivers\\N360x64\\1505000.013\\srtspx64.sys" },
        { SUPHARDNT_ADVERSARY_SYMANTEC_N360, L"\\SystemRoot\\System32\\drivers\\N360x64\\1505000.013\\symds64.sys" },
        { SUPHARDNT_ADVERSARY_SYMANTEC_N360, L"\\SystemRoot\\System32\\drivers\\N360x64\\1505000.013\\symefa64.sys" },
        { SUPHARDNT_ADVERSARY_SYMANTEC_N360, L"\\SystemRoot\\System32\\drivers\\N360x64\\1505000.013\\symelam.sys" },
        { SUPHARDNT_ADVERSARY_SYMANTEC_N360, L"\\SystemRoot\\System32\\drivers\\N360x64\\1505000.013\\symnets.sys" },
        { SUPHARDNT_ADVERSARY_SYMANTEC_N360, L"\\SystemRoot\\System32\\drivers\\symevent64x86.sys" },

        { SUPHARDNT_ADVERSARY_AVAST, L"\\SystemRoot\\System32\\drivers\\aswHwid.sys" },
        { SUPHARDNT_ADVERSARY_AVAST, L"\\SystemRoot\\System32\\drivers\\aswMonFlt.sys" },
        { SUPHARDNT_ADVERSARY_AVAST, L"\\SystemRoot\\System32\\drivers\\aswRdr2.sys" },
        { SUPHARDNT_ADVERSARY_AVAST, L"\\SystemRoot\\System32\\drivers\\aswRvrt.sys" },
        { SUPHARDNT_ADVERSARY_AVAST, L"\\SystemRoot\\System32\\drivers\\aswSnx.sys" },
        { SUPHARDNT_ADVERSARY_AVAST, L"\\SystemRoot\\System32\\drivers\\aswsp.sys" },
        { SUPHARDNT_ADVERSARY_AVAST, L"\\SystemRoot\\System32\\drivers\\aswStm.sys" },
        { SUPHARDNT_ADVERSARY_AVAST, L"\\SystemRoot\\System32\\drivers\\aswVmm.sys" },

        { SUPHARDNT_ADVERSARY_TRENDMICRO, L"\\SystemRoot\\System32\\drivers\\tmcomm.sys" },
        { SUPHARDNT_ADVERSARY_TRENDMICRO, L"\\SystemRoot\\System32\\drivers\\tmactmon.sys" },
        { SUPHARDNT_ADVERSARY_TRENDMICRO, L"\\SystemRoot\\System32\\drivers\\tmevtmgr.sys" },
        { SUPHARDNT_ADVERSARY_TRENDMICRO, L"\\SystemRoot\\System32\\drivers\\tmtdi.sys" },
        { SUPHARDNT_ADVERSARY_TRENDMICRO, L"\\SystemRoot\\System32\\drivers\\tmebc64.sys" },
        { SUPHARDNT_ADVERSARY_TRENDMICRO, L"\\SystemRoot\\System32\\drivers\\tmeevw.sys" },
        { SUPHARDNT_ADVERSARY_TRENDMICRO, L"\\SystemRoot\\System32\\drivers\\tmciesc.sys" },
        { SUPHARDNT_ADVERSARY_TRENDMICRO_SAKFILE, L"\\SystemRoot\\System32\\drivers\\sakfile.sys" },  /* Data Loss Prevention, not officescan. */
        { SUPHARDNT_ADVERSARY_TRENDMICRO, L"\\SystemRoot\\System32\\drivers\\sakcd.sys" },  /* Data Loss Prevention, not officescan. */


        { SUPHARDNT_ADVERSARY_MCAFEE, L"\\SystemRoot\\System32\\drivers\\cfwids.sys" },
        { SUPHARDNT_ADVERSARY_MCAFEE, L"\\SystemRoot\\System32\\drivers\\McPvDrv.sys" },
        { SUPHARDNT_ADVERSARY_MCAFEE, L"\\SystemRoot\\System32\\drivers\\mfeapfk.sys" },
        { SUPHARDNT_ADVERSARY_MCAFEE, L"\\SystemRoot\\System32\\drivers\\mfeavfk.sys" },
        { SUPHARDNT_ADVERSARY_MCAFEE, L"\\SystemRoot\\System32\\drivers\\mfefirek.sys" },
        { SUPHARDNT_ADVERSARY_MCAFEE, L"\\SystemRoot\\System32\\drivers\\mfehidk.sys" },
        { SUPHARDNT_ADVERSARY_MCAFEE, L"\\SystemRoot\\System32\\drivers\\mfencbdc.sys" },
        { SUPHARDNT_ADVERSARY_MCAFEE, L"\\SystemRoot\\System32\\drivers\\mfewfpk.sys" },

        { SUPHARDNT_ADVERSARY_KASPERSKY, L"\\SystemRoot\\System32\\drivers\\kl1.sys" },
        { SUPHARDNT_ADVERSARY_KASPERSKY, L"\\SystemRoot\\System32\\drivers\\klflt.sys" },
        { SUPHARDNT_ADVERSARY_KASPERSKY, L"\\SystemRoot\\System32\\drivers\\klif.sys" },
        { SUPHARDNT_ADVERSARY_KASPERSKY, L"\\SystemRoot\\System32\\drivers\\klim6.sys" },
        { SUPHARDNT_ADVERSARY_KASPERSKY, L"\\SystemRoot\\System32\\drivers\\klkbdflt.sys" },
        { SUPHARDNT_ADVERSARY_KASPERSKY, L"\\SystemRoot\\System32\\drivers\\klmouflt.sys" },
        { SUPHARDNT_ADVERSARY_KASPERSKY, L"\\SystemRoot\\System32\\drivers\\kltdi.sys" },
        { SUPHARDNT_ADVERSARY_KASPERSKY, L"\\SystemRoot\\System32\\drivers\\kneps.sys" },
        { SUPHARDNT_ADVERSARY_KASPERSKY, L"\\SystemRoot\\System32\\klfphc.dll" },

        { SUPHARDNT_ADVERSARY_MBAM, L"\\SystemRoot\\System32\\drivers\\MBAMSwissArmy.sys" },
        { SUPHARDNT_ADVERSARY_MBAM, L"\\SystemRoot\\System32\\drivers\\mwac.sys" },
        { SUPHARDNT_ADVERSARY_MBAM, L"\\SystemRoot\\System32\\drivers\\mbamchameleon.sys" },
        { SUPHARDNT_ADVERSARY_MBAM, L"\\SystemRoot\\System32\\drivers\\mbam.sys" },

        { SUPHARDNT_ADVERSARY_AVG, L"\\SystemRoot\\System32\\drivers\\avgrkx64.sys" },
        { SUPHARDNT_ADVERSARY_AVG, L"\\SystemRoot\\System32\\drivers\\avgmfx64.sys" },
        { SUPHARDNT_ADVERSARY_AVG, L"\\SystemRoot\\System32\\drivers\\avgidsdrivera.sys" },
        { SUPHARDNT_ADVERSARY_AVG, L"\\SystemRoot\\System32\\drivers\\avgidsha.sys" },
        { SUPHARDNT_ADVERSARY_AVG, L"\\SystemRoot\\System32\\drivers\\avgtdia.sys" },
        { SUPHARDNT_ADVERSARY_AVG, L"\\SystemRoot\\System32\\drivers\\avgloga.sys" },
        { SUPHARDNT_ADVERSARY_AVG, L"\\SystemRoot\\System32\\drivers\\avgldx64.sys" },
        { SUPHARDNT_ADVERSARY_AVG, L"\\SystemRoot\\System32\\drivers\\avgdiska.sys" },

        { SUPHARDNT_ADVERSARY_PANDA, L"\\SystemRoot\\System32\\drivers\\PSINAflt.sys" },
        { SUPHARDNT_ADVERSARY_PANDA, L"\\SystemRoot\\System32\\drivers\\PSINFile.sys" },
        { SUPHARDNT_ADVERSARY_PANDA, L"\\SystemRoot\\System32\\drivers\\PSINKNC.sys" },
        { SUPHARDNT_ADVERSARY_PANDA, L"\\SystemRoot\\System32\\drivers\\PSINProc.sys" },
        { SUPHARDNT_ADVERSARY_PANDA, L"\\SystemRoot\\System32\\drivers\\PSINProt.sys" },
        { SUPHARDNT_ADVERSARY_PANDA, L"\\SystemRoot\\System32\\drivers\\PSINReg.sys" },
        { SUPHARDNT_ADVERSARY_PANDA, L"\\SystemRoot\\System32\\drivers\\PSKMAD.sys" },
        { SUPHARDNT_ADVERSARY_PANDA, L"\\SystemRoot\\System32\\drivers\\NNSAlpc.sys" },
        { SUPHARDNT_ADVERSARY_PANDA, L"\\SystemRoot\\System32\\drivers\\NNSHttp.sys" },
        { SUPHARDNT_ADVERSARY_PANDA, L"\\SystemRoot\\System32\\drivers\\NNShttps.sys" },
        { SUPHARDNT_ADVERSARY_PANDA, L"\\SystemRoot\\System32\\drivers\\NNSIds.sys" },
        { SUPHARDNT_ADVERSARY_PANDA, L"\\SystemRoot\\System32\\drivers\\NNSNAHSL.sys" },
        { SUPHARDNT_ADVERSARY_PANDA, L"\\SystemRoot\\System32\\drivers\\NNSpicc.sys" },
        { SUPHARDNT_ADVERSARY_PANDA, L"\\SystemRoot\\System32\\drivers\\NNSPihsw.sys" },
        { SUPHARDNT_ADVERSARY_PANDA, L"\\SystemRoot\\System32\\drivers\\NNSPop3.sys" },
        { SUPHARDNT_ADVERSARY_PANDA, L"\\SystemRoot\\System32\\drivers\\NNSProt.sys" },
        { SUPHARDNT_ADVERSARY_PANDA, L"\\SystemRoot\\System32\\drivers\\NNSPrv.sys" },
        { SUPHARDNT_ADVERSARY_PANDA, L"\\SystemRoot\\System32\\drivers\\NNSSmtp.sys" },
        { SUPHARDNT_ADVERSARY_PANDA, L"\\SystemRoot\\System32\\drivers\\NNSStrm.sys" },
        { SUPHARDNT_ADVERSARY_PANDA, L"\\SystemRoot\\System32\\drivers\\NNStlsc.sys" },

        { SUPHARDNT_ADVERSARY_MSE, L"\\SystemRoot\\System32\\drivers\\MpFilter.sys" },
        { SUPHARDNT_ADVERSARY_MSE, L"\\SystemRoot\\System32\\drivers\\NisDrvWFP.sys" },

        { SUPHARDNT_ADVERSARY_COMODO, L"\\SystemRoot\\System32\\drivers\\cmdguard.sys" },
        { SUPHARDNT_ADVERSARY_COMODO, L"\\SystemRoot\\System32\\drivers\\cmderd.sys" },
        { SUPHARDNT_ADVERSARY_COMODO, L"\\SystemRoot\\System32\\drivers\\inspect.sys" },
        { SUPHARDNT_ADVERSARY_COMODO, L"\\SystemRoot\\System32\\drivers\\cmdhlp.sys" },
        { SUPHARDNT_ADVERSARY_COMODO, L"\\SystemRoot\\System32\\drivers\\cfrmd.sys" },
        { SUPHARDNT_ADVERSARY_COMODO, L"\\SystemRoot\\System32\\drivers\\hmd.sys" },
        { SUPHARDNT_ADVERSARY_COMODO, L"\\SystemRoot\\System32\\guard64.dll" },
        { SUPHARDNT_ADVERSARY_COMODO, L"\\SystemRoot\\System32\\cmdvrt64.dll" },
        { SUPHARDNT_ADVERSARY_COMODO, L"\\SystemRoot\\System32\\cmdkbd64.dll" },
        { SUPHARDNT_ADVERSARY_COMODO, L"\\SystemRoot\\System32\\cmdcsr.dll" },

        { SUPHARDNT_ADVERSARY_ZONE_ALARM, L"\\SystemRoot\\System32\\drivers\\vsdatant.sys" },
        { SUPHARDNT_ADVERSARY_ZONE_ALARM, L"\\SystemRoot\\System32\\AntiTheftCredentialProvider.dll" },

        { SUPHARDNT_ADVERSARY_DIGITAL_GUARDIAN_OLD, L"\\SystemRoot\\System32\\drivers\\dgmaster.sys" },

        { SUPHARDNT_ADVERSARY_CYLANCE, L"\\SystemRoot\\System32\\drivers\\cyprotectdrv32.sys" },
        { SUPHARDNT_ADVERSARY_CYLANCE, L"\\SystemRoot\\System32\\drivers\\cyprotectdrv64.sys" },

        { SUPHARDNT_ADVERSARY_BEYONDTRUST, L"\\SystemRoot\\System32\\drivers\\privman.sys" },
        { SUPHARDNT_ADVERSARY_BEYONDTRUST, L"\\SystemRoot\\System32\\drivers\\privmanfi.sys" },
        { SUPHARDNT_ADVERSARY_BEYONDTRUST, L"\\SystemRoot\\System32\\privman64.dll" },
        { SUPHARDNT_ADVERSARY_BEYONDTRUST, L"\\SystemRoot\\System32\\privman32.dll" },

        { SUPHARDNT_ADVERSARY_AVECTO, L"\\SystemRoot\\System32\\drivers\\PGDriver.sys" },

        { SUPHARDNT_ADVERSARY_SOPHOS, L"\\SystemRoot\\System32\\drivers\\SophosED.sys" }, // not verified

        { SUPHARDNT_ADVERSARY_HORIZON_VIEW_AGENT, L"\\SystemRoot\\System32\\drivers\\vmwicpdr.sys" },
        { SUPHARDNT_ADVERSARY_HORIZON_VIEW_AGENT, L"\\SystemRoot\\System32\\drivers\\ftsjail.sys" },
    };

    uint32_t fFound = 0;

    /*
     * Open the driver object directory.
     */
    UNICODE_STRING NtDirName = RTNT_CONSTANT_UNISTR(L"\\Driver");

    OBJECT_ATTRIBUTES ObjAttr;
    InitializeObjectAttributes(&ObjAttr, &NtDirName, OBJ_CASE_INSENSITIVE, NULL /*hRootDir*/, NULL /*pSecDesc*/);

    HANDLE hDir;
    NTSTATUS rcNt = NtOpenDirectoryObject(&hDir, DIRECTORY_QUERY | FILE_LIST_DIRECTORY, &ObjAttr);
#ifdef VBOX_STRICT
    if (rcNt != STATUS_ACCESS_DENIED) /* non-admin */
        SUPR3HARDENED_ASSERT_NT_SUCCESS(rcNt);
#endif
    if (NT_SUCCESS(rcNt))
    {
        /*
         * Enumerate it, looking for the driver.
         */
        ULONG    uObjDirCtx = 0;
        for (;;)
        {
            uint32_t    abBuffer[_64K + _1K];
            ULONG       cbActual;
            rcNt = NtQueryDirectoryObject(hDir,
                                          abBuffer,
                                          sizeof(abBuffer) - 4, /* minus four for string terminator space. */
                                          FALSE /*ReturnSingleEntry */,
                                          FALSE /*RestartScan*/,
                                          &uObjDirCtx,
                                          &cbActual);
            if (!NT_SUCCESS(rcNt) || cbActual < sizeof(OBJECT_DIRECTORY_INFORMATION))
                break;

            POBJECT_DIRECTORY_INFORMATION pObjDir = (POBJECT_DIRECTORY_INFORMATION)abBuffer;
            while (pObjDir->Name.Length != 0)
            {
                WCHAR wcSaved = pObjDir->Name.Buffer[pObjDir->Name.Length / sizeof(WCHAR)];
                pObjDir->Name.Buffer[pObjDir->Name.Length / sizeof(WCHAR)] = '\0';

                for (uint32_t i = 0; i < RT_ELEMENTS(s_aDrivers); i++)
                    if (RTUtf16ICmpAscii(pObjDir->Name.Buffer, s_aDrivers[i].pszDriver) == 0)
                    {
                        fFound |= s_aDrivers[i].fAdversary;
                        SUP_DPRINTF(("Found driver %s (%#x)\n", s_aDrivers[i].pszDriver, s_aDrivers[i].fAdversary));
                        break;
                    }

                pObjDir->Name.Buffer[pObjDir->Name.Length / sizeof(WCHAR)] = wcSaved;

                /* Next directory entry. */
                pObjDir++;
            }
        }

        NtClose(hDir);
    }
    else
        SUP_DPRINTF(("NtOpenDirectoryObject failed on \\Driver: %#x\n", rcNt));

    /*
     * Look for files.
     */
    for (uint32_t i = 0; i < RT_ELEMENTS(s_aFiles); i++)
    {
        HANDLE              hFile  = RTNT_INVALID_HANDLE_VALUE;
        IO_STATUS_BLOCK     Ios    = RTNT_IO_STATUS_BLOCK_INITIALIZER;
        UNICODE_STRING      UniStrName;
        UniStrName.Buffer = (WCHAR *)s_aFiles[i].pwszFile;
        UniStrName.Length = (USHORT)(RTUtf16Len(s_aFiles[i].pwszFile) * sizeof(WCHAR));
        UniStrName.MaximumLength = UniStrName.Length + sizeof(WCHAR);
        InitializeObjectAttributes(&ObjAttr, &UniStrName, OBJ_CASE_INSENSITIVE, NULL /*hRootDir*/, NULL /*pSecDesc*/);
        rcNt = NtCreateFile(&hFile, GENERIC_READ | SYNCHRONIZE, &ObjAttr, &Ios, NULL /* Allocation Size*/,
                            FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ, FILE_OPEN,
                            FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT, NULL /*EaBuffer*/, 0 /*EaLength*/);
        if (NT_SUCCESS(rcNt) && NT_SUCCESS(Ios.Status))
        {
            fFound |= s_aFiles[i].fAdversary;
            NtClose(hFile);
        }
    }

    /*
     * Log details and upgrade select adversaries.
     */
    SUP_DPRINTF(("supR3HardenedWinFindAdversaries: %#x\n", fFound));
    for (uint32_t i = 0; i < RT_ELEMENTS(s_aFiles); i++)
        if (s_aFiles[i].fAdversary & fFound)
        {
            if (!(s_aFiles[i].fAdversary & SUPHARDNT_ADVERSARY_DIGITAL_GUARDIAN_OLD))
                supR3HardenedLogFileInfo(s_aFiles[i].pwszFile, NULL, 0);
            else
            {
                /*
                 * See if it's a newer version of the driver which doesn't BSODs when we free
                 * its memory.  To use RTStrVersionCompare we do a rough UTF-16 -> ASCII conversion.
                 */
                union
                {
                    char    szFileVersion[64];
                    RTUTF16 wszFileVersion[32];
                } uBuf;
                supR3HardenedLogFileInfo(s_aFiles[i].pwszFile, uBuf.wszFileVersion, RT_ELEMENTS(uBuf.wszFileVersion));
                if (uBuf.wszFileVersion[0])
                {
                    for (uint32_t off = 0; off < RT_ELEMENTS(uBuf.wszFileVersion); off++)
                    {
                        RTUTF16 wch = uBuf.wszFileVersion[off];
                        uBuf.szFileVersion[off] = (char)wch;
                        if (!wch)
                            break;
                    }
                    uBuf.szFileVersion[RT_ELEMENTS(uBuf.wszFileVersion)] = '\0';
#define VER_IN_RANGE(a_pszFirst, a_pszLast) \
    (RTStrVersionCompare(uBuf.szFileVersion, a_pszFirst) >= 0 && RTStrVersionCompare(uBuf.szFileVersion, a_pszLast) <= 0)
                    if (   VER_IN_RANGE("7.3.2.0000", "999999999.9.9.9999")
                        || VER_IN_RANGE("7.3.1.1000", "7.3.1.3000")
                        || VER_IN_RANGE("7.3.0.3000", "7.3.0.999999999")
                        || VER_IN_RANGE("7.2.1.3000", "7.2.999999999.999999999") )
                    {
                        uint32_t const fOldFound = fFound;
                        fFound = (fOldFound & ~SUPHARDNT_ADVERSARY_DIGITAL_GUARDIAN_OLD)
                               |               SUPHARDNT_ADVERSARY_DIGITAL_GUARDIAN_NEW;
                        SUP_DPRINTF(("supR3HardenedWinFindAdversaries: Found newer version: %#x -> %#x\n", fOldFound, fFound));
                    }
                }
            }
        }

    return fFound;
}


extern "C" int main(int argc, char **argv, char **envp);

/**
 * The executable entry point.
 *
 * This is normally taken care of by the C runtime library, but we don't want to
 * get involved with anything as complicated like the CRT in this setup.  So, we
 * it everything ourselves, including parameter parsing.
 */
extern "C" void __stdcall suplibHardenedWindowsMain(void)
{
    RTEXITCODE rcExit = RTEXITCODE_FAILURE;

    g_cSuplibHardenedWindowsMainCalls++;
    g_enmSupR3HardenedMainState = SUPR3HARDENEDMAINSTATE_WIN_EP_CALLED;

    /*
     * Initialize the NTDLL API wrappers. This aims at bypassing patched NTDLL
     * in all the processes leading up the VM process.
     */
    supR3HardenedWinInitImports();
    g_enmSupR3HardenedMainState = SUPR3HARDENEDMAINSTATE_WIN_IMPORTS_RESOLVED;

    /*
     * Notify the parent process that we're probably capable of reporting our
     * own errors.
     */
    if (g_ProcParams.hEvtParent || g_ProcParams.hEvtChild)
    {
        SUPR3HARDENED_ASSERT(g_fSupEarlyProcessInit);

        g_ProcParams.enmRequest = kSupR3WinChildReq_CloseEvents;
        NtSetEvent(g_ProcParams.hEvtParent, NULL);

        NtClose(g_ProcParams.hEvtParent);
        NtClose(g_ProcParams.hEvtChild);
        g_ProcParams.hEvtParent = NULL;
        g_ProcParams.hEvtChild  = NULL;
    }
    else
        SUPR3HARDENED_ASSERT(!g_fSupEarlyProcessInit);

    /*
     * After having resolved imports we patch the LdrInitializeThunk code so
     * that it's more difficult to invade our privacy by CreateRemoteThread.
     * We'll re-enable this after opening the driver or temporarily while respawning.
     */
    supR3HardenedWinDisableThreadCreation();

    /*
     * Init g_uNtVerCombined. (The code is shared with SUPR3.lib and lives in
     * SUPHardenedVerfiyImage-win.cpp.)
     */
    supR3HardenedWinInitVersion(false /*fEarly*/);
    g_enmSupR3HardenedMainState = SUPR3HARDENEDMAINSTATE_WIN_VERSION_INITIALIZED;

    /*
     * Convert the arguments to UTF-8 and open the log file if specified.
     * This must be done as early as possible since the code below may fail.
     */
    PUNICODE_STRING pCmdLineStr = &NtCurrentPeb()->ProcessParameters->CommandLine;
    int    cArgs;
    char **papszArgs = suplibCommandLineToArgvWStub(pCmdLineStr->Buffer, pCmdLineStr->Length / sizeof(WCHAR), &cArgs);

    supR3HardenedOpenLog(&cArgs, papszArgs);

    /*
     * Log information about important system files.
     */
    supR3HardenedLogFileInfo(L"\\SystemRoot\\System32\\ntdll.dll",          NULL /*pwszFileVersion*/, 0 /*cwcFileVersion*/);
    supR3HardenedLogFileInfo(L"\\SystemRoot\\System32\\kernel32.dll",       NULL /*pwszFileVersion*/, 0 /*cwcFileVersion*/);
    supR3HardenedLogFileInfo(L"\\SystemRoot\\System32\\KernelBase.dll",     NULL /*pwszFileVersion*/, 0 /*cwcFileVersion*/);
    supR3HardenedLogFileInfo(L"\\SystemRoot\\System32\\apisetschema.dll",   NULL /*pwszFileVersion*/, 0 /*cwcFileVersion*/);

    /*
     * Scan the system for adversaries, logging information about them.
     */
    g_fSupAdversaries = supR3HardenedWinFindAdversaries();

    /*
     * Get the executable name, make sure it's the long version.
     */
    DWORD cwcExecName = GetModuleFileNameW(GetModuleHandleW(NULL), g_wszSupLibHardenedExePath,
                                           RT_ELEMENTS(g_wszSupLibHardenedExePath));
    if (cwcExecName >= RT_ELEMENTS(g_wszSupLibHardenedExePath))
        supR3HardenedFatalMsg("suplibHardenedWindowsMain", kSupInitOp_Integrity, VERR_BUFFER_OVERFLOW,
                              "The executable path is too long.");

    RTUTF16 wszLong[RT_ELEMENTS(g_wszSupLibHardenedExePath)];
    DWORD cwcLong = GetLongPathNameW(g_wszSupLibHardenedExePath, wszLong, RT_ELEMENTS(wszLong));
    if (cwcLong > 0)
    {
        memcpy(g_wszSupLibHardenedExePath, wszLong, (cwcLong + 1) * sizeof(RTUTF16));
        cwcExecName = cwcLong;
    }

    /* The NT version of it. */
    HANDLE hFile = CreateFileW(g_wszSupLibHardenedExePath, GENERIC_READ, FILE_SHARE_READ, NULL /*pSecurityAttributes*/,
                               OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL /*hTemplateFile*/);
    if (hFile == NULL || hFile == INVALID_HANDLE_VALUE)
        supR3HardenedFatalMsg("suplibHardenedWindowsMain", kSupInitOp_Integrity, RTErrConvertFromWin32(RtlGetLastWin32Error()),
                              "Error opening the executable: %u (%ls).", RtlGetLastWin32Error());
    RT_ZERO(g_SupLibHardenedExeNtPath);
    ULONG cbIgn;
    NTSTATUS rcNt = NtQueryObject(hFile, ObjectNameInformation, &g_SupLibHardenedExeNtPath,
                                  sizeof(g_SupLibHardenedExeNtPath) - sizeof(WCHAR), &cbIgn);
    if (!NT_SUCCESS(rcNt))
        supR3HardenedFatalMsg("suplibHardenedWindowsMain", kSupInitOp_Integrity, RTErrConvertFromNtStatus(rcNt),
                              "NtQueryObject -> %#x (on %ls)\n", rcNt, g_wszSupLibHardenedExePath);
    NtClose(hFile);

    /* The NT executable name offset / dir path length. */
    g_offSupLibHardenedExeNtName = g_SupLibHardenedExeNtPath.UniStr.Length / sizeof(WCHAR);
    while (   g_offSupLibHardenedExeNtName > 1
           && g_SupLibHardenedExeNtPath.UniStr.Buffer[g_offSupLibHardenedExeNtName - 1] != '\\' )
        g_offSupLibHardenedExeNtName--;

    /*
     * Preliminary app binary path init.  May change when SUPR3HardenedMain is
     * called (via main below).
     */
    supR3HardenedWinInitAppBin(SUPSECMAIN_FLAGS_LOC_APP_BIN);

    /*
     * If we've done early init already, register the DLL load notification
     * callback and reinstall the NtDll patches.
     */
    if (g_fSupEarlyProcessInit)
    {
        supR3HardenedWinRegisterDllNotificationCallback();
        supR3HardenedWinReInstallHooks(false /*fFirstCall */);

        /*
         * Flush user APCs before the g_enmSupR3HardenedMainState changes
         * and disables the APC restrictions.
         */
        NtTestAlert();
    }

    /*
     * Call the C/C++ main function.
     */
    SUP_DPRINTF(("Calling main()\n"));
    rcExit = (RTEXITCODE)main(cArgs, papszArgs, NULL);

    /*
     * Exit the process (never return).
     */
    SUP_DPRINTF(("Terminating the normal way: rcExit=%d\n", rcExit));
    suplibHardenedExit(rcExit);
}


/**
 * Reports an error to the parent process via the process parameter structure.
 *
 * @param   pszWhere            Where this error occured, if fatal message. NULL
 *                              if not message.
 * @param   enmWhat             Which init operation went wrong if fatal
 *                              message. kSupInitOp_Invalid if not message.
 * @param   rc                  The status code to report.
 * @param   pszFormat           The format string.
 * @param   va                  The format arguments.
 */
DECLHIDDEN(void) supR3HardenedWinReportErrorToParent(const char *pszWhere, SUPINITOP enmWhat, int rc,
                                                     const char *pszFormat, va_list va)
{
    if (pszWhere)
        RTStrCopy(g_ProcParams.szWhere, sizeof(g_ProcParams.szWhere), pszWhere);
    else
        g_ProcParams.szWhere[0] = '\0';
    RTStrPrintfV(g_ProcParams.szErrorMsg, sizeof(g_ProcParams.szErrorMsg), pszFormat, va);
    g_ProcParams.enmWhat = enmWhat;
    g_ProcParams.rc      = RT_SUCCESS(rc) ? VERR_INTERNAL_ERROR_2 : rc;
    g_ProcParams.enmRequest = kSupR3WinChildReq_Error;

    NtClearEvent(g_ProcParams.hEvtChild);
    NTSTATUS rcNt = NtSetEvent(g_ProcParams.hEvtParent, NULL);
    if (NT_SUCCESS(rcNt))
    {
        LARGE_INTEGER Timeout;
        Timeout.QuadPart = -300000000; /* 30 second */
        /*NTSTATUS rcNt =*/ NtWaitForSingleObject(g_ProcParams.hEvtChild, FALSE /*Alertable*/, &Timeout);
    }
}


/**
 * Routine called by the supR3HardenedEarlyProcessInitThunk assembly routine
 * when LdrInitializeThunk is executed during process initialization.
 *
 * This initializes the Stub and VM processes, hooking NTDLL APIs and opening
 * the device driver before any other DLLs gets loaded into the process.  This
 * greately reduces and controls the trusted code base of the process compared
 * to opening the driver from SUPR3HardenedMain.  It also avoids issues with so
 * call protection software that is in the habit of patching half of the ntdll
 * and kernel32 APIs in the process, making it almost indistinguishable from
 * software that is up to no good.  Once we've opened vboxdrv, the process
 * should be locked down so thighly that only kernel software and csrss can mess
 * with the process.
 */
DECLASM(uintptr_t) supR3HardenedEarlyProcessInit(void)
{
    /*
     * When the first thread gets here we wait for the parent to continue with
     * the process purifications.  The primary thread must execute for image
     * load notifications to trigger, at least in more recent windows versions.
     * The old trick of starting a different thread that terminates immediately
     * thus doesn't work.
     *
     * We are not allowed to modify any data at this point because it will be
     * reset by the child process purification the parent does when we stop. To
     * sabotage thread creation during purification, and to avoid unnecessary
     * work for the parent, we reset g_ProcParams before signalling the parent
     * here.
     */
    if (g_enmSupR3HardenedMainState != SUPR3HARDENEDMAINSTATE_NOT_YET_CALLED)
    {
        NtTerminateThread(0, 0);
        return 0x22; /* crash */
    }

    /* Retrieve the data we need. */
    uintptr_t uNtDllAddr = ASMAtomicXchgPtrT(&g_ProcParams.uNtDllAddr, 0, uintptr_t);
    if (!RT_VALID_PTR(uNtDllAddr))
    {
        NtTerminateThread(0, 0);
        return 0x23; /* crash */
    }

    HANDLE hEvtChild  = g_ProcParams.hEvtChild;
    HANDLE hEvtParent = g_ProcParams.hEvtParent;
    if (   hEvtChild  == NULL
        || hEvtChild  == RTNT_INVALID_HANDLE_VALUE
        || hEvtParent == NULL
        || hEvtParent == RTNT_INVALID_HANDLE_VALUE)
    {
        NtTerminateThread(0, 0);
        return 0x24; /* crash */
    }

    /* Resolve the APIs we need. */
    PFNNTWAITFORSINGLEOBJECT    pfnNtWaitForSingleObject;
    PFNNTSETEVENT               pfnNtSetEvent;
    supR3HardenedWinGetVeryEarlyImports(uNtDllAddr, &pfnNtWaitForSingleObject, &pfnNtSetEvent);

    /* Signal the parent that we're ready for purification. */
    RT_ZERO(g_ProcParams);
    g_ProcParams.enmRequest = kSupR3WinChildReq_PurifyChildAndCloseHandles;
    NTSTATUS rcNt = pfnNtSetEvent(hEvtParent, NULL);
    if (rcNt != STATUS_SUCCESS)
        return 0x33; /* crash */

    /* Wait up to 2 mins for the parent to exorcise evil. */
    LARGE_INTEGER Timeout;
    Timeout.QuadPart = -1200000000; /* 120 second */
    rcNt = pfnNtWaitForSingleObject(hEvtChild, FALSE /*Alertable (never alertable before hooking!) */, &Timeout);
    if (rcNt != STATUS_SUCCESS)
        return 0x34; /* crash */

    /*
     * We're good to go, work global state and restore process parameters.
     * Note that we will not restore uNtDllAddr since that is our first defence
     * against unwanted threads (see above).
     */
    g_enmSupR3HardenedMainState = SUPR3HARDENEDMAINSTATE_WIN_EARLY_INIT_CALLED;
    g_fSupEarlyProcessInit      = true;

    g_ProcParams.hEvtChild      = hEvtChild;
    g_ProcParams.hEvtParent     = hEvtParent;
    g_ProcParams.enmRequest     = kSupR3WinChildReq_Error;
    g_ProcParams.rc             = VINF_SUCCESS;

    /*
     * Initialize the NTDLL imports that we consider usable before the
     * process has been initialized.
     */
    supR3HardenedWinInitImportsEarly(uNtDllAddr);
    g_enmSupR3HardenedMainState = SUPR3HARDENEDMAINSTATE_WIN_EARLY_IMPORTS_RESOLVED;

    /*
     * Init g_uNtVerCombined as well as we can at this point.
     */
    supR3HardenedWinInitVersion(true /*fEarly*/);

    /*
     * Convert the arguments to UTF-8 so we can open the log file if specified.
     * We may have to normalize the pointer on older windows version (not w7/64 +).
     * Note! This leaks memory at present.
     */
    PRTL_USER_PROCESS_PARAMETERS pUserProcParams = NtCurrentPeb()->ProcessParameters;
    UNICODE_STRING CmdLineStr = pUserProcParams->CommandLine;
    if (   CmdLineStr.Buffer != NULL
        && !(pUserProcParams->Flags & RTL_USER_PROCESS_PARAMS_FLAG_NORMALIZED) )
        CmdLineStr.Buffer = (WCHAR *)((uintptr_t)CmdLineStr.Buffer + (uintptr_t)pUserProcParams);
    int    cArgs;
    char **papszArgs = suplibCommandLineToArgvWStub(CmdLineStr.Buffer, CmdLineStr.Length / sizeof(WCHAR), &cArgs);
    supR3HardenedOpenLog(&cArgs, papszArgs);
    SUP_DPRINTF(("supR3HardenedVmProcessInit: uNtDllAddr=%p g_uNtVerCombined=%#x (stack ~%p)\n",
                 uNtDllAddr, g_uNtVerCombined, &Timeout));

    /*
     * Set up the direct system calls so we can more easily hook NtCreateSection.
     */
    RTERRINFOSTATIC ErrInfo;
    supR3HardenedWinInitSyscalls(true /*fReportErrors*/, RTErrInfoInitStatic(&ErrInfo));

    /*
     * Determine the executable path and name.  Will NOT determine the windows style
     * executable path here as we don't need it.
     */
    SIZE_T cbActual = 0;
    rcNt = NtQueryVirtualMemory(NtCurrentProcess(), &g_ProcParams, MemorySectionName, &g_SupLibHardenedExeNtPath,
                                sizeof(g_SupLibHardenedExeNtPath) - sizeof(WCHAR), &cbActual);
    if (   !NT_SUCCESS(rcNt)
        || g_SupLibHardenedExeNtPath.UniStr.Length == 0
        || g_SupLibHardenedExeNtPath.UniStr.Length & 1)
        supR3HardenedFatal("NtQueryVirtualMemory/MemorySectionName failed in supR3HardenedVmProcessInit: %#x\n", rcNt);

    /* The NT executable name offset / dir path length. */
    g_offSupLibHardenedExeNtName = g_SupLibHardenedExeNtPath.UniStr.Length / sizeof(WCHAR);
    while (   g_offSupLibHardenedExeNtName > 1
           && g_SupLibHardenedExeNtPath.UniStr.Buffer[g_offSupLibHardenedExeNtName - 1] != '\\' )
        g_offSupLibHardenedExeNtName--;

    /*
     * Preliminary app binary path init.  May change when SUPR3HardenedMain is called.
     */
    supR3HardenedWinInitAppBin(SUPSECMAIN_FLAGS_LOC_APP_BIN);

    /*
     * Initialize the image verification stuff (hooks LdrLoadDll and NtCreateSection).
     */
    supR3HardenedWinInit(0, false /*fAvastKludge*/);

    /*
     * Open the driver.
     */
    if (cArgs >= 1 && suplibHardenedStrCmp(papszArgs[0], SUPR3_RESPAWN_1_ARG0) == 0)
    {
        SUP_DPRINTF(("supR3HardenedVmProcessInit: Opening vboxdrv stub...\n"));
        supR3HardenedWinOpenStubDevice();
        g_enmSupR3HardenedMainState = SUPR3HARDENEDMAINSTATE_WIN_EARLY_STUB_DEVICE_OPENED;
    }
    else if (cArgs >= 1 && suplibHardenedStrCmp(papszArgs[0], SUPR3_RESPAWN_2_ARG0) == 0)
    {
        SUP_DPRINTF(("supR3HardenedVmProcessInit: Opening vboxdrv...\n"));
        supR3HardenedMainOpenDevice();
        g_enmSupR3HardenedMainState = SUPR3HARDENEDMAINSTATE_WIN_EARLY_REAL_DEVICE_OPENED;
    }
    else
        supR3HardenedFatal("Unexpected first argument '%s'!\n", papszArgs[0]);

    /*
     * Reinstall the NtDll patches since there is a slight possibility that
     * someone undid them while we where busy opening the device.
     */
    supR3HardenedWinReInstallHooks(false /*fFirstCall */);

    /*
     * Restore the LdrInitializeThunk code so we can initialize the process
     * normally when we return.
     */
    SUP_DPRINTF(("supR3HardenedVmProcessInit: Restoring LdrInitializeThunk...\n"));
    PSUPHNTLDRCACHEENTRY pLdrEntry;
    int rc = supHardNtLdrCacheOpen("ntdll.dll", &pLdrEntry, RTErrInfoInitStatic(&ErrInfo));
    if (RT_FAILURE(rc))
        supR3HardenedFatal("supR3HardenedVmProcessInit: supHardNtLdrCacheOpen failed on NTDLL: %Rrc %s\n",
                           rc, ErrInfo.Core.pszMsg);

    uint8_t *pbBits;
    rc = supHardNtLdrCacheEntryGetBits(pLdrEntry, &pbBits, uNtDllAddr, NULL, NULL, RTErrInfoInitStatic(&ErrInfo));
    if (RT_FAILURE(rc))
        supR3HardenedFatal("supR3HardenedVmProcessInit: supHardNtLdrCacheEntryGetBits failed on NTDLL: %Rrc %s\n",
                           rc, ErrInfo.Core.pszMsg);

    RTLDRADDR uValue;
    rc = RTLdrGetSymbolEx(pLdrEntry->hLdrMod, pbBits, uNtDllAddr, UINT32_MAX, "LdrInitializeThunk", &uValue);
    if (RT_FAILURE(rc))
        supR3HardenedFatal("supR3HardenedVmProcessInit: Failed to find LdrInitializeThunk (%Rrc).\n", rc);

    PVOID pvLdrInitThunk = (PVOID)(uintptr_t)uValue;
    SUPR3HARDENED_ASSERT_NT_SUCCESS(supR3HardenedWinProtectMemory(pvLdrInitThunk, 16, PAGE_EXECUTE_READWRITE));
    memcpy(pvLdrInitThunk, pbBits + ((uintptr_t)uValue - uNtDllAddr), 16);
    SUPR3HARDENED_ASSERT_NT_SUCCESS(supR3HardenedWinProtectMemory(pvLdrInitThunk, 16, PAGE_EXECUTE_READ));

    SUP_DPRINTF(("supR3HardenedVmProcessInit: Returning to LdrInitializeThunk...\n"));
    return (uintptr_t)pvLdrInitThunk;
}

